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,
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
examining a function object you can fully reconstruct the function's
signature. Unfortunately this information is stored in an inconvenient
@ -35,16 +35,12 @@ function introspection easier for Python programmers.
Signature Object
================
A Signature object represents the overall signature of a function.
It stores a `Parameter object`_ for each parameter accepted by the
function, as well as information specific to the function itself.
A Signature object represents the call signature of a function and
its return annotation. For each parameter accepted by the function
it stores a `Parameter object`_ in its ``parameters`` collection.
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
The annotation for the return type of the function if specified.
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``).
* bind(\*args, \*\*kwargs) -> BoundArguments
Creates a mapping from positional and keyword arguments to
parameters. Raises a ``BindError`` if the passed arguments
do not match the signature.
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.)
* 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,
do not affect the function itself.
@ -75,7 +85,7 @@ The structure of the Parameter object is:
* name : str
The name of the parameter as a string.
* 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.
* annotation : object
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
thoroughly documented.
Parameter objects support testing for equality. Two Parameter
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``.
Two parameters are equal when all their attributes are equal.
BoundArguments Object
@ -113,7 +119,7 @@ to the function's parameters.
Has the following public attributes:
* 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.
* args : tuple
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
``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):
@ -148,7 +154,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 it
is not ``None`` - return a deepcopy of it
- If it is ``None`` and the object is an instance of
``BuiltinFunction``, raise a ``ValueError``
@ -160,29 +166,43 @@ The function implements the following algorithm:
- 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
``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
a new ``Signature`` from its ``partial.func`` attribute, and
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__)``
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]_.
A python issue was also created: [#issue]_.
An implementation for Python 3.3 can be found at [#impl]_.
The python issue tracking the patch is [#issue]_.
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``
@ -201,60 +221,49 @@ following downsides:
Examples
========
Function Signature Renderer
---------------------------
Visualizing Callable Objects' Signature
---------------------------------------
::
def render_signature(signature):
'''Renders function definition by its signature.
from inspect import signature
from functools import partial, wraps
Example:
>>> def test(a:'foo', *, b:'bar', c=True, **kwargs:None) -> 'spam':
... pass
class FooMeta(type):
def __new__(mcls, name, bases, dct, *, bar:bool=False):
return super().__new__(mcls, name, bases, dct)
>>> render_signature(inspect.signature(test))
test(a:'foo', *, b:'bar', c=True, **kwargs:None) -> 'spam'
'''
def __init__(cls, name, bases, dct, **kwargs):
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
if hasattr(param, 'annotation'):
formatted = '{}:{!r}'.format(formatted, param.annotation)
if hasattr(param, 'default'):
formatted = '{}={!r}'.format(formatted, param.default)
class Foo(metaclass=FooMeta):
def __init__(self, spam:int=42):
self.spam = spam
# Handle *args and **kwargs -like parameters
if param.is_args:
formatted = '*' + formatted
elif param.is_kwargs:
formatted = '**' + formatted
def __call__(self, a, b, *, c) -> tuple:
return a, b, c
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'):
rendered += ' -> {!r}'.format(signature.return_annotation)
The script will output:
::
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