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

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:  * returnannotation : 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.covarnames). +    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. +  * bindpartial(*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, *, returnannotation) -> Signature

Shouldn't it be something like this:

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 returnannotation to override the +    corresponding properties of the base signature.  To remove +    returnannotation 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) +    >>> newsig = sig.replace(returnannotation="new return annotation") +    >>> newsig is not sig +    True +    >>> newsig.returnannotation == sig.returnannotation +    True

Should be False here, right?

+    >>> newsig.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, *, returnannotation)

Same here as with Signature.replace().

+    Default Signature constructor.  Accepts an optional sequence +    of Parameter objects, and an optional returnannotation. +    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.fromfunction(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.fromfunction((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 POSITIONALONLY +    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.KEYWORDONLY, 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 VARKEYWORD or VARPOSITIONAL 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 sharedvars(*sharedargs): """Decorator factory that defines shared variables that are @@ -280,10 +372,12 @@ def wrapper(*args, **kwds): fullargs = sharedargs + args return f(*fullargs, **kwds) + # Override signature -            sig = wrapper.signature = signature(f) -            for _ in sharedargs: -                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 @@ >>> formatsignature(Foo().call) '(a, b, *, c) -> tuple' +    >>> formatsignature(Foo.spam) +    '(a)' + >>> formatsignature(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