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,
|
||||
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
|
||||
|
|
Loading…
Reference in New Issue