[Python-Dev] PEP 362: 4th edition

Jim J. Jewett jimjjewett at gmail.com
Sat Jun 16 05:56:31 CEST 2012


Summary:

    *Every* Parameter attribute is optional, even name.  (Think of
    builtins, even if they aren't automatically supported yet.)
    So go ahead and define some others that are sometimes useful.

    Instead of defining a BoundArguments class, just return a copy
    of the Signature, with "value" attributes added to the Parameters.
    
    Use subclasses to distinguish the parameter kind.  (Replacing
    most of the is_ methods from the 3rd version.)

    [is_]implemented is important information, but the API isn't
    quite right; even with tweaks, maybe we should wait a version
    before freezing it on the base class.  But I would be happy
    to have Larry create a Signature for the os.* functions,
    whether that means a subclass or just an extra instance
    attribute.

    I favor passing a class to Signature.format, because so many of
    the formatting arguments would normally change in parallel.
    But my tolerance for nested structures may be unusually high.

    I make some more specific suggestions below.

    
In http://mail.python.org/pipermail/python-dev/2012-June/120305.html
Yury Selivanov wrote:

> A Signature object has the following public attributes and methods:

> * return_annotation : object
>    The annotation for the return type of the function if specified.
>    If the function has no annotation for its return type, this
>    attribute is not set.

This means users must already be prepared to use hasattr with the
Signature as well as the Parameters -- in which case, I don't see any
harm in a few extra optional properties.

I would personally prefer to see the name (and qualname) and docstring,
but it would make perfect sense to implement these by keeping a weakref
to the original callable, and just delegating there unless/until the
properties are explicitly changed.  I suspect others will have a use
for additional delegated attributes, such as the self of boundmethods.

I do agree that __eq__ and __hash__ should depend at most on the
parameters (including their order) and the annotation.

> * parameters : OrderedDict
>     An ordered mapping of parameters' names to the corresponding
>     Parameter objects (keyword-only arguments are in the same order
>     as listed in ``code.co_varnames``).

For a specification, that feels a little too tied to the specific
implementation.  How about:

     Parameters will be ordered as they are in the function declaration.

or even just:

     Positional parameters will be ordered as they are in the function
     declaration.

because:
    def f(*, a=4, b=5): pass

and:
    def f(*, b=5, a=4): pass

should probably have equal signatures.


Wild thought:  Instead of just *having* an OrderedDict of Parameters,
should a Signature *be* that OrderedDict (with other attributes)?
That is, should signature(testfn)["foo"] get the foo parameter?


> * bind(\*args, \*\*kwargs) -> BoundArguments
>     Creates a mapping from positional and keyword arguments to
>     parameters.  Raises a ``BindError`` (subclass of ``TypeError``)
>     if the passed arguments do not match the signature.
> * bind_partial(\*args, \*\*kwargs) -> BoundArguments
>     Works the same way as ``bind()``, but allows the omission
>     of some required arguments (mimics ``functools.partial``
>     behavior.)

Are those descriptions actually correct?

I would expect the mapping to be from parameters (or parameter names)
to values extracted from *args and **kwargs.

And I'm not sure the current patch does even that, since it seems to
instead return a non-Mapping object (but with a mapping attribute)
that could be used to re-create *args, **kwargs in canonical form.
(Though that canonicalization is valuable for calls; it might even
be worth an as_call method.)


I think it should be explicit that this mapping does not include
parameters which would be filled by default arguments.  In fact, if
you stick with this interface, I would like a 3rd method that does
fill out everything.


But I think it would be simpler to just add an optional attribute
to each Parameter instance, and let bind fill that in on the copies,
so that the return value is also a Signature.  (No need for the
BoundArguments class.)  Then the user can decide whether or not to
plug in the defaults for missing values.


> * format(...) -> str
>     Formats the Signature object to a string.  Optional arguments allow
>     for custom render functions for parameter names,
>     annotations and default values, along with custom separators.

I think it should state explicitly that by default, the return value
will be a string that could be used to declare an equivalent function,
if "Signature" were replaced with "def funcname".

There are enough customization parameters that would often be changed
together (e.g., to produce HTML output) that it might make sense to use
overridable class defaults -- or even to make format a class itself.

I also think it would make sense to delegate formatting the individual
parameters to the parameter objects.  Yes, this does mean that the
subclasses would be more than markers classes.

> It's possible to test Signatures for equality.  Two signatures
> are equal when they have equal parameters and return annotations.

I would be more explicit about parameter order mattering.  Perhaps:

    It's possible to test Signatures for equality.  Two signatures are
    equal when their parameters are equal, their positional parameters
    appear in the same order, and they have equal return annotations.



> The structure of the Parameter object is:

> * name : str
>     The name of the parameter as a string.

If there is no name, as with some builtins, will this be "", None or
not set?

(3rd edition)
> * is_keyword_only : bool ...
> * is_args : bool ...
> * is_kwargs : bool ...

(4th edition)
> ... Parameter.POSITIONAL_ONLY ...
> ... Parameter.POSITIONAL_OR_KEYWORD ...
> ... Parameter.KEYWORD_ONLY ...
> ... Parameter.VAR_POSITIONAL ...
> ... Parameter.VAR_KEYWORD ...

This set has already grown, and I can think of others I would like to
use.  (Pseudo-parameters, such as a method's self instance, or an
auxiliary variable.)

To me, that suggests marker subclasses and isinstance checking.

    class BaseParameter: ...

    # These two really are different
    class ArgsParameter(BaseParameter): ...
    class KwargsParameter(BaseParameter): ...

    class KeywordParameter(BaseParameter): ...
    class PositionalParameter(BaseParameter): ...
    class Parameter(KeywordParameter, PositionalParameter): ... 

(I'm not sure that normal parameters should really be the bottom of an
inheritance diamond, as opposed to a sibling.)

It may also be worth distinguishing BoundParameter at the class level,
but since the only distinction is an extra attribute (value), I'm not
sure about that.

Question:  These names are getting long.  Was there a reason not to use
Sig and Param?


> * implemented : bool

This information is valuable, and should be at least an optional part
of a signature (or a specific parameter, if there are no interactions).
But I don't think a boolean is sufficient.

At a minimum, let it return an object such that bool(obj) indicates
whether non-default values are ever useful, but obj *could* provide
more information.  For example I would be happy with the following:

    >>> s=signature(open("myfile").seek)
    >>> v=s.parameters["whence"].is_implemented
    >>> v[0]
    True
    >>> v["SEEK_DATA"]
    False
    
That said, if the decision is to leave is_implemented up to subclasses
for now, I won't object.


> Two parameters are equal when all their attributes are equal.

I think that it would be better to specify which attributes matter,
and let them be equal so long as those attributes matched.  I'll try
to post details on the ticket, but roughly only the attributes
specifically mentioned in this PEP should matter.  I'm not sure
if positional parameters should also check position, or if that
can be left to the Signature.

                    

-jJ

-- 

If there are still threading problems with my replies, please 
email me with details, so that I can try to resolve them.  -jJ



More information about the Python-Dev mailing list