Update to PEP 362 from Yury.

This commit is contained in:
Brett Cannon 2012-06-13 22:15:01 -04:00
parent a0f82b8887
commit e36d9f7b49
1 changed files with 75 additions and 66 deletions

View File

@ -16,7 +16,7 @@ Abstract
======== ========
Python has always supported powerful introspection capabilities, Python has always supported powerful introspection capabilities,
including introspecting functions and methods. (For the rest of including introspecting functions and methods (for the rest of
this PEP, "function" refers to both functions and methods). By this PEP, "function" refers to both functions and methods). By
examining a function object you can fully reconstruct the function's examining a function object you can fully reconstruct the function's
signature. Unfortunately this information is stored in an inconvenient signature. Unfortunately this information is stored in an inconvenient
@ -35,16 +35,12 @@ function introspection easier for Python programmers.
Signature Object Signature Object
================ ================
A Signature object represents the overall signature of a function. A Signature object represents the call signature of a function and
It stores a `Parameter object`_ for each parameter accepted by the its return annotation. For each parameter accepted by the function
function, as well as information specific to the function itself. it stores a `Parameter object`_ in its ``parameters`` collection.
A Signature object has the following public attributes and methods: A Signature object has the following public attributes and methods:
* name : str
Name of the function.
* qualname : str
Fully qualified name of the function.
* return_annotation : object * return_annotation : object
The annotation for the return type of the function if specified. The annotation for the return type of the function if specified.
If the function has no annotation for its return type, this If the function has no annotation for its return type, this
@ -55,8 +51,22 @@ A Signature object has the following public attributes and methods:
as listed in ``code.co_varnames``). as listed in ``code.co_varnames``).
* bind(\*args, \*\*kwargs) -> BoundArguments * bind(\*args, \*\*kwargs) -> BoundArguments
Creates a mapping from positional and keyword arguments to Creates a mapping from positional and keyword arguments to
parameters. Raises a ``BindError`` if the passed arguments parameters. Raises a ``BindError`` (subclass of ``TypeError``)
do not match the signature. 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.)
* 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.
Signature implements the ``__str__`` method, which fallbacks to the
``Signature.format()`` call.
It's possible to test Signatures for equality. Two signatures
are equal when they have equal parameters and return annotations.
Changes to the Signature object, or to any of its data members, Changes to the Signature object, or to any of its data members,
do not affect the function itself. do not affect the function itself.
@ -75,7 +85,7 @@ The structure of the Parameter object is:
* name : str * name : str
The name of the parameter as a string. The name of the parameter as a string.
* default : object * default : object
The default value for the parameter if specified. If the The default value for the parameter, if specified. If the
parameter has no default value, this attribute is not set. parameter has no default value, this attribute is not set.
* annotation : object * annotation : object
The annotation for the parameter if specified. If the The annotation for the parameter if specified. If the
@ -97,11 +107,7 @@ The structure of the Parameter object is:
all conditions where ``is_implemented`` may be False be all conditions where ``is_implemented`` may be False be
thoroughly documented. thoroughly documented.
Parameter objects support testing for equality. Two Parameter Two parameters are equal when all their attributes are equal.
objects are equal, when all their properties are equal. Those
who need to test if one signature has the same parameters as
another, can do a direct comparison of ``Signature.parameters``
collections: ``signature(foo).parameters == signature(bar).parameters``.
BoundArguments Object BoundArguments Object
@ -113,7 +119,7 @@ to the function's parameters.
Has the following public attributes: Has the following public attributes:
* arguments : OrderedDict * arguments : OrderedDict
An ordered mutable mapping of parameters' names to arguments' values. An ordered, mutable mapping of parameters' names to arguments' values.
Does not contain arguments' default values. Does not contain arguments' default values.
* args : tuple * args : tuple
Tuple of positional arguments values. Dynamically computed from Tuple of positional arguments values. Dynamically computed from
@ -125,7 +131,7 @@ Has the following public attributes:
The ``arguments`` attribute should be used in conjunction with The ``arguments`` attribute should be used in conjunction with
``Signature.parameters`` for any arguments processing purposes. ``Signature.parameters`` for any arguments processing purposes.
``args`` and ``kwargs`` properties should be used to invoke functions: ``args`` and ``kwargs`` properties can be used to invoke functions:
:: ::
def test(a, *, b): def test(a, *, b):
@ -148,7 +154,7 @@ The function implements the following algorithm:
- If the object is not callable - raise a TypeError - If the object is not callable - raise a TypeError
- If the object has a ``__signature__`` attribute and if it - If the object has a ``__signature__`` attribute and if it
is not ``None`` - return it is not ``None`` - return a deepcopy of it
- If it is ``None`` and the object is an instance of - If it is ``None`` and the object is an instance of
``BuiltinFunction``, raise a ``ValueError`` ``BuiltinFunction``, raise a ``ValueError``
@ -160,29 +166,43 @@ The function implements the following algorithm:
- Or else construct a new ``Signature`` object and return it - Or else construct a new ``Signature`` object and return it
- if the object is a method or a classmethod, construct and return - If the object is a method or a classmethod, construct and return
a new ``Signature`` object, with its first parameter (usually a new ``Signature`` object, with its first parameter (usually
``self`` or ``cls``) removed ``self`` or ``cls``) removed
- If the object is a class return ``signature(object.__init__)`` - If the object is a staticmethod, construct and return
a new ``Signature`` object
- If the object is an instance of ``functools.partial``, construct - If the object is an instance of ``functools.partial``, construct
a new ``Signature`` from its ``partial.func`` attribute, and a new ``Signature`` from its ``partial.func`` attribute, and
account for already bound ``partial.args`` and ``partial.kwargs`` account for already bound ``partial.args`` and ``partial.kwargs``
- If the object is a class or metaclass:
- 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,
return a Signature object for it
- If the object has a ``__init__`` method defined in its class,
return a Signature object for it
- Return ``signature(object.__call__)`` - 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. is not automatically cached. If, however, the Signature object was
explicitly cached by the user, ``signature()`` returns a new deepcopy
of it on each invocation.
An implementation for Python 3.3 can be found here: [#impl]_. An implementation for Python 3.3 can be found at [#impl]_.
A python issue was also created: [#issue]_. The python issue tracking the patch is [#issue]_.
Design Considerations Design Considerations
===================== =====================
No Implicit Caching of Signature Objects No implicit caching of Signature objects
---------------------------------------- ----------------------------------------
The first PEP design had a provision for implicit caching of ``Signature`` The first PEP design had a provision for implicit caching of ``Signature``
@ -201,60 +221,49 @@ following downsides:
Examples Examples
======== ========
Function Signature Renderer Visualizing Callable Objects' Signature
--------------------------- ---------------------------------------
:: ::
def render_signature(signature): from inspect import signature
'''Renders function definition by its signature. from functools import partial, wraps
Example:
>>> def test(a:'foo', *, b:'bar', c=True, **kwargs:None) -> 'spam': class FooMeta(type):
... pass def __new__(mcls, name, bases, dct, *, bar:bool=False):
return super().__new__(mcls, name, bases, dct)
>>> render_signature(inspect.signature(test)) def __init__(cls, name, bases, dct, **kwargs):
test(a:'foo', *, b:'bar', c=True, **kwargs:None) -> 'spam' return super().__init__(name, bases, dct)
'''
result = []
render_kw_only_separator = True
for param in signature.parameters.values():
formatted = param.name
# Add annotation and default value class Foo(metaclass=FooMeta):
if hasattr(param, 'annotation'): def __init__(self, spam:int=42):
formatted = '{}:{!r}'.format(formatted, param.annotation) self.spam = spam
if hasattr(param, 'default'):
formatted = '{}={!r}'.format(formatted, param.default)
# Handle *args and **kwargs -like parameters def __call__(self, a, b, *, c) -> tuple:
if param.is_args: return a, b, c
formatted = '*' + formatted
elif param.is_kwargs:
formatted = '**' + formatted
if param.is_args:
# OK, we have an '*args'-like parameter, so we won't need
# a '*' to separate keyword-only arguments
render_kw_only_separator = False
elif param.is_keyword_only and render_kw_only_separator:
# We have a keyword-only parameter to render and we haven't
# rendered an '*args'-like parameter before, so add a '*'
# separator to the parameters list ("foo(arg1, *, arg2)" case)
result.append('*')
# This condition should be only triggered once, so
# reset the flag
render_kw_only_separator = False
result.append(formatted) print('FooMeta >', str(signature(FooMeta)))
print('Foo >', str(signature(Foo)))
print('Foo.__call__ >', str(signature(Foo.__call__)))
print('Foo().__call__ >', str(signature(Foo().__call__)))
print('partial(Foo().__call__, 1, c=3) >',
str(signature(partial(Foo().__call__, 1, c=3))))
print('partial(partial(Foo().__call__, 1, c=3), 2, c=20) >',
str(signature(partial(partial(Foo().__call__, 1, c=3), 2, c=20))))
rendered = '{}({})'.format(signature.name, ', '.join(result))
if hasattr(signature, 'return_annotation'): The script will output:
rendered += ' -> {!r}'.format(signature.return_annotation) ::
return rendered FooMeta > (name, bases, dct, *, bar:bool=False)
Foo > (spam:int=42)
Foo.__call__ > (self, a, b, *, c) -> tuple
Foo().__call__ > (a, b, *, c) -> tuple
partial(Foo().__call__, 1, c=3) > (b, *, c=3) -> tuple
partial(partial(Foo().__call__, 1, c=3), 2, c=20) > (*, c=20) -> tuple
Annotation Checker Annotation Checker