The latest changes from Yury Selivanov. I can almost taste the acceptance!

This commit is contained in:
Larry Hastings 2012-06-21 01:44:15 -07:00
parent 11dadc9c41
commit ff243784df
1 changed files with 128 additions and 31 deletions

View File

@ -42,23 +42,58 @@ it stores a `Parameter object`_ in its ``parameters`` collection.
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
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``.
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
>>> new_sig.parameters == sig.parameters
True
There are two ways to instantiate a Signature class:
* Signature(parameters, *, return_annotation)
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 @@ have equal return annotations.
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 @@ kinds of parameters with many subtle semantic differences. We
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 @@ The structure of the Parameter object is:
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 @@ The structure of the Parameter object is:
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 @@ Has the following public attributes:
* 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 @@ The ``arguments`` attribute should be used in conjunction with
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
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 @@ The function implements the following algorithm:
- 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 @@ The function implements the following algorithm:
- If the object is a an instance of ``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
- If the object is an instance of ``functools.partial``, construct
a new ``Signature`` from its ``partial.func`` attribute, and
@ -196,15 +274,15 @@ The function implements the following algorithm:
- 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
----------------------------------------
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 @@ Let's define some classes and functions:
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 @@ Let's define some classes and functions:
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 @@ Now, in the python REPL:
>>> 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'