From e36d9f7b4980baadfbd17efa1fe00d9cb4a228f6 Mon Sep 17 00:00:00 2001 From: Brett Cannon Date: Wed, 13 Jun 2012 22:15:01 -0400 Subject: [PATCH] Update to PEP 362 from Yury. --- pep-0362.txt | 141 +++++++++++++++++++++++++++------------------------ 1 file changed, 75 insertions(+), 66 deletions(-) diff --git a/pep-0362.txt b/pep-0362.txt index 12dd71784..9235e252c 100644 --- a/pep-0362.txt +++ b/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