[Python-Dev] pep 362 - 5th edition

Yury Selivanov yselivanov.ml at gmail.com
Wed Jun 20 03:22:58 CEST 2012


On 2012-06-19, at 8:39 PM, Nick Coghlan wrote:

> On Wed, Jun 20, 2012 at 3:53 AM, Yury Selivanov <yselivanov.ml at gmail.com> wrote:
>> On 2012-06-19, at 1:03 PM, Ethan Furman wrote:
>>> At some point it was suggested that Signature be put in provisionally so we could modify the API if needed -- are we doing that?
>> 
>> Well, it doesn't have much of an API right now (just few methods)
> 
> Right, provisional API status is a fairly blunt instrument that we
> should only use if we can't find any other way to allow the standard
> library to progress.
> 
> In this particular case, we have a superior alternative: distill the
> API down to the bare minimum that is needed to provide a more robust,
> cross-implementation format for describing callable signatures. We can
> then implement that bare minimum API for 3.3 as the foundation for
> higher level layered APIs that offer more flexibility, and/or capture
> more information about the callables.
> 
> Further comments on the PEP and implementation:
> 
> 1. The PEP should specify the constructor signatures for Signature and
> Parameter objects (I'd also prefer it if "kind" was permitted as a
> positional argument)

+1

> 2. The constructor for Parameter objects should require that names for
> positional-only parameters start with "<" and end with ">" to ensure
> they can always be distinguished from standard parameters in signature
> string representations and in BoundArguments.parameters

+1

> 3. The standard Signature constructor should accept an iterable of
> Parameter objects directly (with the return annotation as an optional
> keyword-only "annotation" argument) and enforce the following
> constraints:
> - permitted parameter binding order is strictly (POSITIONAL_ONLY,
> POSITIONAL_OR_KEYWORD, VAR_POSITIONAL, KEYWORD_ONLY, VAR_KEYWORD)
> - all parameter names must be distinct (otherwise bind() won't work properly)
> - if a positional only parameter is not named (param.name is None), it
> is given a name based on its position in the parameter list ("<0>",
> "<1>", etc)

+1

> 4. The current Signature constructor should become a "from_function"
> class method

+1

> With these changes, the following would become straightforward:
> 
>>>> def f1(a): pass
>>>> str(signature(f1))
>    (a)
>>>> def f2(*args): a, = args
>>>> f.__signature__ = Signature([Parameter("<a>",
> Parameter.POSITIONAL_ONLY])
>>>> str(signature(f2))
>    (<a>)
>>>> def f3(*args): a, = args
>>>> f.__signature__ = Signature([Parameter(None, Parameter.POSITIONAL_ONLY])
>>>> str(signature(f3))
>    (<0>)
> 
> 5. Given the updated constructor signature, we can revisit the
> question of immutable signature objects (even just using read-only
> properties for public attributes and exposing a dict proxy for the
> parameter list). Instead of mutating the parameter list, you would
> instead write code like:
>    new_sig = Signature(old_sig.parameters[1:])

I think that mocking immutability here is a better decision than
to implement a truly immutable object in C.  Read-only properties
and a dict-proxy for Signature.parameters will work.

> In my opinion, that's a *much* nicer approach than copying an existing
> signature object and mutating it.

+1

> 6. I think "return_annotation" can safely be abbreviated to just
> "annotation". The fact it is on the Signature object rather than an
> individual parameter is enough to identify it as the return
> annotation.

I'm not sure about this one.  'return_annotation' is a very 
self-descriptive and clear name.

> 7. The idea of immutable Signature objects does highlight an annoyance
> with the "attribute may be missing" style APIs. To actually duplicate
> a signature correctly, including its return annotation (and assuming
> the attribute is renamed), you would have to do something like:
> 
>    try:
>        note = {"annotation": old_sig.annotation}
>    except AttributeError:
>        note = {}
>    new_sig = Signature(old_sig.parameters[1:], **note)
> 
> There's an alternative approach to optional attributes that's often
> easier to work with: expose them as containers. In this case, since we
> want to make them easy to pass as keyword-only arguments, one way to
> achieve that would be expose an "optional" attribute on both Signature
> and Parameter objects. Then the above would be written as:
> 
>    new_sig = Signature(old_sig.parameters[1:], **old_sig.optional)
> 
> And copying a Parameter would be:
>    new_param = Parameter("new name", old_param.kind, **old_param.optional)
> 
> If we even keep that at all for the initial version of the API, the
> direct "default" and "annotation" attributes would just be read-only
> properties that accessed the "optional" container (reporting
> AttributeError if the corresponding attribute was missing)

+0.  I think that 'optional' is a bit unusual attribute for the stdlib, 
but it will work if we make Signature immutable.

> 8. Not essential, but I suggest moving most of the parameter
> formatting details to a Parameter.__str__ method

+1

> 9. The PEP should explicitly note that we're taking a deliberately
> strict approach to the notion of signature and parameter equivalence
> by assuming that all parameter names have semantic significance.
> Looser checks that ignore the names of positional and variable keyword
> parameters can be handled with Signature.bind() or by implementing
> custom key or comparison functions.

+1 

> 10. Similar to the discussion of convenience properties on Signature
> objects themselves, I now think we should drop the "args" and "kwargs"

Big -1 on this one.  Look at the current implementation of those
properties - it's quite complex.  One of the major points of new
API is to allow easy modifications of arguments.  Without .args &
.kwargs it will be a PITA to call a function.

Imagine, that the "check types" example from the PEP is modified
to coerce arguments to specified types.  It won't be possible
to do without .args & .kwargs.  I, for instance, use this API to bind, 
validate, and coerce arguments for RPC calls. The whole point for me 
to work on this PEP was to make these types of functionality easy to
implement.

> properties from the initial version of BoundArguments. Instead, I
> propose the following attributes:
>  - arguments (mapping of parameter names to values supplied as arguments)
>  - defaults (mapping of unbound parameter names with defaults to
> their default values)

Why would you need 'defaults'?  It's very easy to compile that list 
manually (and I believe the use cases will be limited)

>  - unbound (set of unbound names, always empty for bind(), may have
> entries for bind_partial())

This may be practical.  But again - those are easy to deduce from
'BoundArguments.arguments' and 'Signature.parameters'.


In summary - I like everything you've proposed, except comment #10.

-
Yury


More information about the Python-Dev mailing list