Update to PEP 362 from Yury.
This commit is contained in:
parent
a0f82b8887
commit
e36d9f7b49
141
pep-0362.txt
141
pep-0362.txt
|
@ -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
|
||||||
|
|
Loading…
Reference in New Issue