[Python-Dev] [Python-checkins] peps: The latest changes from Yury Selivanov. I can almost taste the acceptance!

Eric Snow ericsnowcurrently at gmail.com
Thu Jun 21 17:34:10 CEST 2012


On Thu, Jun 21, 2012 at 2:44 AM, larry.hastings
<python-checkins at python.org> wrote:
> http://hg.python.org/peps/rev/1edf1cecae7d
> changeset:   4472:1edf1cecae7d
> user:        Larry Hastings <larry at hastings.org>
> date:        Thu Jun 21 01:44:15 2012 -0700
> summary:
>  The latest changes from Yury Selivanov.  I can almost taste the acceptance!
>
> files:
>  pep-0362.txt |  159 +++++++++++++++++++++++++++++++-------
>  1 files changed, 128 insertions(+), 31 deletions(-)
>
>
> diff --git a/pep-0362.txt b/pep-0362.txt
> --- a/pep-0362.txt
> +++ b/pep-0362.txt
> @@ -42,23 +42,58 @@
>  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.
> +    The "return" annotation for the function. If the function
> +    has no "return" annotation, this attribute is not set.
> +
>  * 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``).
> +    Parameter objects.
> +
>  * bind(\*args, \*\*kwargs) -> BoundArguments
>     Creates a mapping from positional and keyword arguments to
>     parameters.  Raises a ``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.)  Raises a ``TypeError`` if the passed arguments do
>     not match the signature.
>
> +* replace(parameters, \*, return_annotation) -> Signature

Shouldn't it be something like this:

* replace_(*parameters, [return_annotation]) -> Signature

Or is parameters supposed to be a dict/OrderedDict of replacements/additions?

> +    Creates a new Signature instance based on the instance
> +    ``replace`` was invoked on.  It is possible to pass different
> +    ``parameters`` and/or ``return_annotation`` to override the
> +    corresponding properties of the base signature.  To remove
> +    ``return_annotation`` from the copied ``Signature``, pass in
> +    ``Signature.empty``.

Can you likewise remove parameters this way?

> +
> +Signature objects are immutable.  Use ``Signature.replace()`` to
> +make a modified copy:
> +::
> +
> +    >>> sig = signature(foo)
> +    >>> new_sig = sig.replace(return_annotation="new return annotation")
> +    >>> new_sig is not sig
> +    True
> +    >>> new_sig.return_annotation == sig.return_annotation
> +    True

Should be False here, right?

> +    >>> new_sig.parameters == sig.parameters
> +    True

An example of replacing parameters would also be good here.

> +
> +There are two ways to instantiate a Signature class:
> +
> +* Signature(parameters, *, return_annotation)

Same here as with Signature.replace().

> +    Default Signature constructor.  Accepts an optional sequence
> +    of ``Parameter`` objects, and an optional ``return_annotation``.
> +    Parameters sequence is validated to check that there are no
> +    parameters with duplicate names, and that the parameters
> +    are in the right order, i.e. positional-only first, then
> +    positional-or-keyword, etc.
> +* Signature.from_function(function)
> +    Returns a Signature object reflecting the signature of the
> +    function passed in.
> +
>  It's possible to test Signatures for equality.  Two signatures are
>  equal when their parameters are equal, their positional and
>  positional-only parameters appear in the same order, and they
> @@ -67,9 +102,14 @@
>  Changes to the Signature object, or to any of its data members,
>  do not affect the function itself.
>
> -Signature also implements ``__str__`` and ``__copy__`` methods.
> -The latter creates a shallow copy of Signature, with all Parameter
> -objects copied as well.
> +Signature also implements ``__str__``:
> +::
> +
> +    >>> str(Signature.from_function((lambda *args: None)))
> +    '(*args)'
> +
> +    >>> str(Signature())
> +    '()'
>
>
>  Parameter Object
> @@ -80,20 +120,22 @@
>  propose a rich Parameter object designed to represent any possible
>  function parameter.
>
> -The structure of the Parameter object is:
> +A Parameter object has the following public attributes and methods:
>
>  * name : str
> -    The name of the parameter as a string.
> +    The name of the parameter as a string.  Must be a valid
> +    python identifier name (with the exception of ``POSITIONAL_ONLY``
> +    parameters, which can have it set to ``None``.)
>
>  * default : object
> -    The default value for the parameter, if specified.  If the
> -    parameter has no default value, this attribute is not set.
> +    The default value for the parameter.  If the parameter has no
> +    default value, this attribute is not set.
>
>  * annotation : object
> -    The annotation for the parameter if specified.  If the
> -    parameter has no annotation, this attribute is not set.
> +    The annotation for the parameter.  If the parameter has no
> +    annotation, this attribute is not set.
>
> -* kind : str
> +* kind
>     Describes how argument values are bound to the parameter.
>     Possible values:
>
> @@ -101,7 +143,7 @@
>          as a positional argument.
>
>          Python has no explicit syntax for defining positional-only
> -         parameters, but many builtin and extension module functions
> +         parameters, but many built-in and extension module functions
>          (especially those that accept only one or two parameters)
>          accept them.
>
> @@ -124,9 +166,30 @@
>          that aren't bound to any other parameter. This corresponds
>          to a "\*\*kwds" parameter in a Python function definition.
>
> +* replace(\*, name, kind, default, annotation) -> Parameter
> +    Creates a new Parameter instance based on the instance
> +    ``replaced`` was invoked on.  To override a Parameter
> +    attribute, pass the corresponding argument.  To remove
> +    an attribute from a ``Parameter``, pass ``Parameter.empty``.
> +
> +
>  Two parameters are equal when they have equal names, kinds, defaults,
>  and annotations.
>
> +Parameter objects are immutable.  Instead of modifying a Parameter object,
> +you can use ``Parameter.replace()`` to create a modified copy like so:
> +::
> +
> +    >>> param = Parameter('foo', Parameter.KEYWORD_ONLY, default=42)
> +    >>> str(param)
> +    'foo=42'
> +
> +    >>> str(param.replace())
> +    'foo=42'
> +
> +    >>> str(param.replace(default=Parameter.empty, annotation='spam'))
> +    "foo:'spam'"
> +
>
>  BoundArguments Object
>  =====================
> @@ -138,7 +201,8 @@
>
>  * arguments : OrderedDict
>     An ordered, mutable mapping of parameters' names to arguments' values.
> -    Does not contain arguments' default values.
> +    Contains only explicitly bound arguments.  Arguments for
> +    which ``bind()`` relied on a default value are skipped.
>  * args : tuple
>     Tuple of positional arguments values.  Dynamically computed from
>     the 'arguments' attribute.
> @@ -159,6 +223,23 @@
>     ba = sig.bind(10, b=20)
>     test(*ba.args, **ba.kwargs)
>
> +Arguments which could be passed as part of either ``*args`` or ``**kwargs``
> +will be included only in the ``BoundArguments.args`` attribute.  Consider the

Why wouldn't the kwargs go into BoundArguments.kwargs?

> +following example:
> +::
> +
> +    def test(a=1, b=2, c=3):
> +        pass
> +
> +    sig = signature(test)
> +    ba = sig.bind(a=10, c=13)
> +
> +    >>> ba.args
> +    (10,)
> +
> +    >>> ba.kwargs:
> +    {'c': 13}
> +
>
>  Implementation
>  ==============
> @@ -172,7 +253,7 @@
>     - If the object is not callable - raise a TypeError
>
>     - If the object has a ``__signature__`` attribute and if it
> -      is not ``None`` - return a shallow copy of it
> +      is not ``None`` - return it
>
>     - If it has a ``__wrapped__`` attribute, return
>       ``signature(object.__wrapped__)``
> @@ -180,12 +261,9 @@
>     - If the object is a an instance of ``FunctionType`` construct

s/``FunctionType`` construct/``FunctionType``, construct/

>       and return a new ``Signature`` for it
>
> -    - If the object is a method or a classmethod, construct and return
> -      a new ``Signature`` object, with its first parameter (usually
> -      ``self`` or ``cls``) removed
> -
> -    - If the object is a staticmethod, construct and return
> -      a new ``Signature`` object
> +    - If the object is a method, construct and return a new ``Signature``
> +      object, with its first parameter (usually ``self`` or ``cls``)
> +      removed

It may be worth explicitly clarify that it refers to bound methods
(and classmethods) here.

Also, inspect.getfullargspec() doesn't strip out the self/cls.  Would
it be okay to store that implicit first argument (self/cls) on the
Signature object somehow?  Explicit is better than implicit.  It's
certainly a very special case: the only implicit (and unavoidable)
arguments of any kind of callable.  If the self were stored on the
Signature, I'd also expect that Signature.replace() would leave it out
(as would any other copy mechanism).

>
>     - If the object is an instance of ``functools.partial``, construct
>       a new ``Signature`` from its ``partial.func`` attribute, and
> @@ -196,15 +274,15 @@
>         - If the object's type has a ``__call__`` method defined in
>           its MRO, return a Signature for it
>
> -        - If the object has a ``__new__`` method defined in its class,
> +        - If the object has a ``__new__`` method defined in its MRO,
>           return a Signature object for it
>
> -        - If the object has a ``__init__`` method defined in its class,
> +        - If the object has a ``__init__`` method defined in its MRO,
>           return a Signature object for it
>
>     - Return ``signature(object.__call__)``
>
> -Note, that the ``Signature`` object is created in a lazy manner, and
> +Note that the ``Signature`` object is created in a lazy manner, and
>  is not automatically cached.  If, however, the Signature object was
>  explicitly cached by the user, ``signature()`` returns a new shallow copy
>  of it on each invocation.
> @@ -236,11 +314,21 @@
>  ----------------------------------------
>
>  Some functions may not be introspectable in certain implementations of
> -Python.  For example, in CPython, builtin functions defined in C provide
> +Python.  For example, in CPython, built-in functions defined in C provide
>  no metadata about their arguments.  Adding support for them is out of
>  scope for this PEP.
>
>
> +Signature and Parameter equivalence
> +-----------------------------------
> +
> +We assume that parameter names have semantic significance--two
> +signatures are equal only when their corresponding parameters have
> +the exact same names.  Users who want looser equivalence tests, perhaps
> +ignoring names of VAR_KEYWORD or VAR_POSITIONAL parameters, will
> +need to implement those themselves.
> +
> +
>  Examples
>  ========
>
> @@ -270,6 +358,10 @@
>         def __call__(self, a, b, *, c) -> tuple:
>             return a, b, c
>
> +        @classmethod
> +        def spam(cls, a):
> +            return a
> +
>
>     def shared_vars(*shared_args):
>         """Decorator factory that defines shared variables that are
> @@ -280,10 +372,12 @@
>             def wrapper(*args, **kwds):
>                 full_args = shared_args + args
>                 return f(*full_args, **kwds)
> +
>             # Override signature
> -            sig = wrapper.__signature__ = signature(f)
> -            for __ in shared_args:
> -                sig.parameters.popitem(last=False)
> +            sig = signature(f)
> +            sig = sig.replace(tuple(sig.parameters.values())[1:])
> +            wrapper.__signature__ = sig
> +
>             return wrapper
>         return decorator
>
> @@ -313,6 +407,9 @@
>     >>> format_signature(Foo().__call__)
>     '(a, b, *, c) -> tuple'
>
> +    >>> format_signature(Foo.spam)
> +    '(a)'
> +
>     >>> format_signature(partial(Foo().__call__, 1, c=3))
>     '(b, *, c=3) -> tuple'

I'm really impressed by the great work on this and how well the PEP
process has been working here.  This is a great addition to Python!

-eric


More information about the Python-Dev mailing list