From 2a621d5a1994fd462066a8dedd3b855af8552569 Mon Sep 17 00:00:00 2001 From: Carl Meyer Date: Fri, 25 May 2012 09:13:13 -0600 Subject: [PATCH 01/16] Move PEP 405 to Accepted state. --- pep-0405.txt | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/pep-0405.txt b/pep-0405.txt index 3939740bf..484d4c1b5 100644 --- a/pep-0405.txt +++ b/pep-0405.txt @@ -4,7 +4,7 @@ Version: $Revision$ Last-Modified: $Date$ Author: Carl Meyer BDFL-Delegate: Nick Coghlan -Status: Draft +Status: Accepted Type: Standards Track Content-Type: text/x-rst Created: 13-Jun-2011 @@ -284,15 +284,15 @@ Include files Current virtualenv handles include files in this way: -On POSIX systems where the installed Python's include files are found -in ``${base_prefix}/include/pythonX.X``, virtualenv creates -``${venv}/include/`` and symlink ``${base_prefix}/include/pythonX.X`` +On POSIX systems where the installed Python's include files are found in +``${base_prefix}/include/pythonX.X``, virtualenv creates +``${venv}/include/`` and symlinks ``${base_prefix}/include/pythonX.X`` to ``${venv}/include/pythonX.X``. On Windows, where Python's include files are found in ``{{ sys.prefix }}/Include`` and symlinks are not reliably available, virtualenv copies ``{{ sys.prefix }}/Include`` to ``${venv}/Include``. This ensures that extension modules built and -installed within the virtualenv will always find the Python header -files they need in the expected location relative to ``sys.prefix``. +installed within the virtualenv will always find the Python header files +they need in the expected location relative to ``sys.prefix``. This solution is not ideal when an extension module installs its own header files, as the default installation location for those header @@ -466,10 +466,10 @@ than ``sys.site_prefix`` or the appropriate ``site`` API to find site-packages directories. The most notable case is probably `setuptools`_ and its fork -`distribute`_, which mostly use ``distutils``and ``sysconfig`` APIs, +`distribute`_, which mostly use ``distutils`` and ``sysconfig`` APIs, but do use ``sys.prefix`` directly to build up a list of site -directories for pre-flight checking where ``pth`` files can usefully -be placed. +directories for pre-flight checking where ``pth`` files can usefully be +placed. Otherwise, a `Google Code Search`_ turns up what appears to be a roughly even mix of usage between packages using ``sys.prefix`` to From b11439d120be74a3509bec1b0f65acb3d1f4e3e1 Mon Sep 17 00:00:00 2001 From: Barry Warsaw Date: Mon, 4 Jun 2012 17:49:40 -0400 Subject: [PATCH 02/16] Mark PEP 421 as final --- pep-0421.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pep-0421.txt b/pep-0421.txt index bff928810..b1064d05d 100644 --- a/pep-0421.txt +++ b/pep-0421.txt @@ -4,7 +4,7 @@ Version: $Revision$ Last-Modified: $Date$ Author: Eric Snow BDFL-Delegate: Barry Warsaw -Status: Accepted +Status: Final Type: Standards Track Content-Type: text/x-rst Created: 26-April-2012 From ea2123adbb6270e965c40a8f927356433bbb213b Mon Sep 17 00:00:00 2001 From: Nick Coghlan Date: Tue, 5 Jun 2012 22:09:20 +1000 Subject: [PATCH 03/16] Add PEP 422: Dynamic Class Decorators --- pep-0422.txt | 168 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 168 insertions(+) create mode 100644 pep-0422.txt diff --git a/pep-0422.txt b/pep-0422.txt new file mode 100644 index 000000000..fabffdd82 --- /dev/null +++ b/pep-0422.txt @@ -0,0 +1,168 @@ +PEP: 422 +Title: Dynamic class decorators +Version: $Revision$ +Last-Modified: $Date$ +Author: Nick Coghlan +Status: Draft +Type: Standards Track +Content-Type: text/x-rst +Created: 5-Jun-2012 +Post-History: 5-Jun-2012 + + +Abstract +======== + +Classes currently support two mechanisms for modification of the class at +definition time: metaclasses and lexical decorators. + +Metaclasses can be awkward and challenging to use correctly in conjunction +with multiple inheritance and lexical decorators don't interact with class +inheritance at all. + +This PEP proposes a new mechanism for dynamic class decoration that +interacts more cleanly with class inheritance mechanisms. + + +Specification +============= + +This PEP proposes that a new step be added to the class creation process, +after the metaclass invocation to construct the class instance and before +the application of lexical decorators. + +This step will walk the class MRO in reverse order, looking for +``__decorators__`` entries in each class dictionary. These entries are +expected to be iterables that are also walked in reverse order to retrieve +class decorators that are automatically applied to the class being defined:: + + for entry in reversed(cls.mro()): + decorators = entry.__dict__.get("__decorators__", ()) + for deco in reversed(decorators): + cls = deco(cls) + +This step in the class creation process will be an implicit part of the +class statement and also part of the behaviour of ``types.new_class()``. + + +Rationale +========= + +When decorator support was added to classes, the lexical decoration syntax +was copied directly from function decorators:: + + @decorator + class Example: + # Subclasses will not be decorated automatically + pass + +This mechanism works well, so long as it is considered acceptable that the +decorator is *not* applied automatically to any subclasses. If it is +desired that the behaviour be inherited, it is currently necessary to +make the step up to defining a `custom metaclass`_:: + + class DynamicDecorators(type): + """Metaclass for dynamic decorator support + + Creates the class normally, then runs through the MRO looking for + __decorators__ attributes and applying the contained decorators to + the newly created class + """ + def __new__(meta, name, bases, ns): + cls = super(DynamicDecorators, meta).__new__(meta, name, bases, ns) + for entry in reversed(cls.mro()): + decorators = entry.__dict__.get("__decorators__", ()) + for deco in reversed(decorators): + cls = deco(cls) + return cls + + class Example(metaclass=DynamicDecorators): + # Subclasses *will* be decorated automatically + __decorators__ = [decorator] + +The main potential problem with this approach, is that it can place +significant constraints on the type heirarchy, as it requires that all +metaclasses used be well behaved with respect to multiple inheritance. + +By making dynamic decorators an inherent part of the class creation process, +many current use cases of metaclasses may be replaced with dynamic decorators +instead, greatly reducing the likelihood of metaclass conflicts, as well +as being substantially easier to write correctly in the first place. + + +Design Discussion +================= + + +Allowing metaclasses to override the dynamic decoration process +--------------------------------------------------------------- + +This PEP does not provide a mechanism that allows metaclasses to override the +dynamic decoration process. If this feature is deemed desirable in the +future, then it can be added by moving the functionality described in +this PEP into a new method on the metaclass (for example, ``__decorate__``), +with ``type`` providing a suitable default implementation that matches +the behaviour described here. + +This PEP chose the simplicity of the current approach, as lexical decorators +are currently outside the scope of metaclass control, so it seems reasonable +to pursue the simpler strategy in the absence of a solid use case for +making this behaviour configurable. + + +Iterating over decorator entries in reverse order +------------------------------------------------- + +This order was chosen to match the layout of lexical decorators when +converted to ordinary function calls. Just as the following are equivalent:: + + @deco2 + @deco1 + class C: + pass + + class C: + pass + C = deco2(deco1(C)) + +So too will the following be roughly equivalent (aside from inheritance):: + + class C: + __decorators__ = [deco2, deco1] + + class C: + pass + C = deco2(deco1(C)) + + +Iterating over the MRO in reverse order +--------------------------------------- + +The order of iteration over the MRO for decorator application was chosen to +match the order of actual call *evaluation* when using ``super`` to invoke +parent class implementations: the first method to run to completion is that +closest to the base of the class hierarchy. + + +References +========== + +.. _custom metaclass: + https://bitbucket.org/ncoghlan/misc/src/default/pep422.py + + +Copyright +========= + +This document has been placed in the public domain. + + +.. + Local Variables: + mode: indented-text + indent-tabs-mode: nil + sentence-end-double-space: t + fill-column: 70 + coding: utf-8 + End: + From 6e2ef0fce7518847c230ddc1e93fe482c533929b Mon Sep 17 00:00:00 2001 From: Nick Coghlan Date: Tue, 5 Jun 2012 22:16:52 +1000 Subject: [PATCH 04/16] PEP 420 is done --- pep-0420.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pep-0420.txt b/pep-0420.txt index 171ced4cf..431a70477 100644 --- a/pep-0420.txt +++ b/pep-0420.txt @@ -3,7 +3,7 @@ Title: Implicit Namespace Packages Version: $Revision$ Last-Modified: $Date$ Author: Eric V. Smith -Status: Accepted +Status: Final Type: Standards Track Content-Type: text/x-rst Created: 19-Apr-2012 From e8131ca8b6cc0b46827ddb3b566ae8fd5e851822 Mon Sep 17 00:00:00 2001 From: Brett Cannon Date: Tue, 5 Jun 2012 20:20:53 -0400 Subject: [PATCH 05/16] Update for PEP 362 by Yury Selivanov (mostly) and Larry Hastings. --- pep-0362.txt | 504 ++++++++++++++++++++++++++------------------------- 1 file changed, 258 insertions(+), 246 deletions(-) diff --git a/pep-0362.txt b/pep-0362.txt index 833947402..545b01a35 100644 --- a/pep-0362.txt +++ b/pep-0362.txt @@ -2,269 +2,313 @@ PEP: 362 Title: Function Signature Object Version: $Revision$ Last-Modified: $Date$ -Author: Brett Cannon , Jiwon Seo +Author: Brett Cannon , Jiwon Seo , + Yury Selivanov , Larry Hastings Status: Draft Type: Standards Track Content-Type: text/x-rst Created: 21-Aug-2006 -Python-Version: 2.6 -Post-History: 05-Sep-2007 +Python-Version: 3.3 +Post-History: 04-Jun-2012 Abstract ======== Python has always supported powerful introspection capabilities, -including that for functions and methods (for the rest of this PEP the -word "function" refers to both functions and methods). Taking a -function object, you can fully reconstruct the function's signature. -Unfortunately it is a little unruly having to look at all the -different attributes to pull together complete information for a -function's signature. +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 +manner, and is spread across a half-dozen deeply nested attributes. -This PEP proposes an object representation for function signatures. -This should help facilitate introspection on functions for various -uses. The introspection information contains all possible information -about the parameters in a signature (including Python 3.0 features). +This PEP proposes a new representation for function signatures. +The new representation contains all necessary information about a function +and its parameters, and makes introspection easy and straightforward. -This object, though, is not meant to replace existing ways of -introspection on a function's signature. The current solutions are -there to make Python's execution work in an efficient manner. The -proposed object representation is only meant to help make application -code have an easier time to query a function on its signature. - - -Purpose -======= - -An object representation of a function's call signature should provide -an easy way to introspect what a function expects as arguments. It -does not need to be a "live" representation, though; the signature can -be inferred once and stored without changes to the signature object -representation affecting the function it represents (but this is an -`Open Issues`_). - -Indirection of signature introspection can also occur. If a -decorator took a decorated function's signature object and set it on -the decorating function then introspection could be redirected to what -is actually expected instead of the typical ``*args, **kwargs`` -signature of decorating functions. +However, this object does not replace the existing function +metadata, which is used by Python itself to execute those +functions. The new metadata object is intended solely to make +function introspection easier for Python programmers. Signature Object ================ -The overall signature of an object is represented by the Signature -object. This object is to store a `Parameter object`_ for each -parameter in the signature. It is also to store any information -about the function itself that is pertinent to the signature. +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 has the following structure attributes: +A Signature object has the following public attributes and methods: * name : str - Name of the function. This is not fully qualified because - function objects for methods do not know the class they are - contained within. This makes functions and methods - indistinguishable from one another when passed to decorators, - preventing proper creation of a fully qualified name. -* var_args : str - Name of the variable positional parameter (i.e., ``*args``), if - present, or the empty string. -* var_kw_args : str - Name of the variable keyword parameter (i.e., ``**kwargs``), if - present, or the empty string. -* var_annotations: dict(str, object) - Dict that contains the annotations for the variable parameters. - The keys are of the variable parameter with values of the - annotation. If an annotation does not exist for a variable - parameter then the key does not exist in the dict. + Name of the function. +* qualname : str + Fully qualified name of the function. * return_annotation : object - If present, the attribute is set to the annotation for the return - type of the function. -* parameters : list(Parameter) - List of the parameters of the function as represented by - Parameter objects in the order of its definition (keyword-only - arguments are in the order listed by ``code.co_varnames``). -* bind(\*args, \*\*kwargs) -> dict(str, object) - Create a mapping from arguments to parameters. The keys are the - names of the parameter that an argument maps to with the value - being the value the parameter would have if this function was - called with the given arguments. + The annotation for the return type of the function if specified. + If the function has no annotation for its return type, this + attribute is not set. +* parameters : OrderedDict + An ordered mapping of parameters' names to the corresponding + Parameter objects (keyword-only arguments are in the same order + as listed in ``code.co_varnames``). +* bind(\*args, \*\*kwargs) -> BoundArguments + Creates a mapping from positional and keyword arguments to + parameters. -Signature objects also have the following methods: +Once a Signature object is created for a particular function, +it's cached in the ``__signature__`` attribute of that function. -* __getitem__(self, key : str) -> Parameter - Returns the Parameter object for the named parameter. -* __iter__(self) - Returns an iterator that returns Parameter objects in their - sequential order based on their 'position' attribute. - -The Signature object is stored in the ``__signature__`` attribute of -a function. When it is to be created is discussed in -`Open Issues`_. +Changes to the Signature object, or to any of its data members, +do not affect the function itself. Parameter Object ================ -A function's signature is made up of several parameters. Python's -different kinds of parameters is quite large and rich and continues to -grow. Parameter objects represent any possible parameter. - -Originally the plan was to represent parameters using a list of -parameter names on the Signature object along with various dicts keyed -on parameter names to disseminate the various pieces of information -one can know about a parameter. But the decision was made to -incorporate all information about a parameter in a single object so -as to make extending the information easier. This was originally put -forth by Talin and the preferred form of Guido (as discussed at the -2006 Google Sprint). +Python's expressive syntax means functions can accept many different +kinds of parameters with many subtle semantic differences. We +propose a rich Parameter object designed to represent any possible +function parameter. The structure of the Parameter object is: -* name : (str | tuple(str)) - The name of the parameter as a string if it is not a tuple. If - the argument is a tuple then a tuple of strings is used. -* position : int - The position of the parameter within the signature of the - function (zero-indexed). For keyword-only parameters the position - value is arbitrary while not conflicting with positional - parameters. The suggestion of setting the attribute to None or -1 - to represent keyword-only parameters was rejected to prevent - variable type usage and as a possible point of errors, - respectively. -* default_value : object - The default value for the parameter, if present, else the - attribute does not exist. -* keyword_only : bool +* name : str + The name of the parameter as a string. +* default : object + 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 + parameter has no annotation, this attribute is not set. +* is_keyword_only : bool True if the parameter is keyword-only, else False. -* annotation - Set to the annotation for the parameter. If ``has_annotation`` is - False then the attribute does not exist to prevent accidental use. +* is_args : bool + True if the parameter accepts variable number of arguments + (``\*args``-like), else False. +* is_kwargs : bool + True if the parameter accepts variable number of keyword + arguments (``\*\*kwargs``-like), else False. +* is_implemented : bool + True if the parameter is implemented for use. Some platforms + implement functions but can't support specific parameters + (e.g. "mode" for os.mkdir). Passing in an unimplemented + parameter may result in the parameter being ignored, + or in NotImplementedError being raised. It is intended that + all conditions where ``is_implemented`` may be False be + thoroughly documented. + + +BoundArguments Object +===================== + +Result of a ``Signature.bind`` call. Holds the mapping of arguments +to the function's parameters. + +Has the following public attributes: + +* arguments : OrderedDict + 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 + the 'arguments' attribute. +* kwargs : dict + Dict of keyword arguments values. Dynamically computed from + the 'arguments' attribute. + +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: +:: + + def test(a, *, b): + ... + + sig = signature(test) + ba = sig.bind(10, b=20) + test(*ba.args, **ba.kwargs) Implementation ============== -An implementation can be found in Python's sandbox [#impl]_. -There is a function named ``signature()`` which -returns the value stored on the ``__signature__`` attribute if it -exists, else it creates the Signature object for the -function and sets ``__signature__``. For methods this is stored -directly on the im_func function object since that is what decorators -work with. +An implementation for Python 3.3 can be found here: [#impl]_. +A python issue was also created: [#issue]_. + +The implementation adds a new function ``signature()`` to the +``inspect`` module. ``signature()`` returns the value stored +on the ``__signature__`` attribute if it exists, otherwise it +creates the Signature object for the function and caches it in +the function's ``__signature__``. (For methods this is stored +directly in the ``__func__`` function object, since that is what +decorators work with.) Examples ======== +Function Signature Renderer +--------------------------- +:: + + def render_signature(signature): + '''Renders function definition by its signature. + + Example: + + >>> def test(a:'foo', *, b:'bar', c=True, **kwargs:None) -> 'spam': + ... pass + + >>> render_signature(inspect.signature(test)) + test(a:'foo', *, b:'bar', c=True, **kwargs:None) -> 'spam' + ''' + + 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) + + # Handle *args and **kwargs -like parameters + if param.is_args: + 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) + + rendered = '{}({})'.format(signature.name, ', '.join(result)) + + if hasattr(signature, 'return_annotation'): + rendered += ' -> {!r}'.format(signature.return_annotation) + + return rendered + + Annotation Checker ------------------ :: - def quack_check(fxn): - """Decorator to verify arguments and return value quack as they should. + import inspect + import functools - Positional arguments. - >>> @quack_check - ... def one_arg(x:int): pass - ... - >>> one_arg(42) - >>> one_arg('a') - Traceback (most recent call last): - ... - TypeError: 'a' does not quack like a + def checktypes(func): + '''Decorator to verify arguments and return types + Example: - *args - >>> @quack_check - ... def var_args(*args:int): pass - ... - >>> var_args(*[1,2,3]) - >>> var_args(*[1,'b',3]) - Traceback (most recent call last): - ... - TypeError: *args contains a a value that does not quack like a + >>> @checktypes + ... def test(a:int, b:str) -> int: + ... return int(a * b) - **kwargs - >>> @quack_check - ... def var_kw_args(**kwargs:int): pass - ... - >>> var_kw_args(**{'a': 1}) - >>> var_kw_args(**{'a': 'A'}) - Traceback (most recent call last): - ... - TypeError: **kwargs contains a value that does not quack like a + >>> test(10, '1') + 1111111111 - Return annotations. - >>> @quack_check - ... def returned(x) -> int: return x - ... - >>> returned(42) - 42 - >>> returned('a') - Traceback (most recent call last): - ... - TypeError: the return value 'a' does not quack like a + >>> test(10, 1) + Traceback (most recent call last): + ... + ValueError: foo: wrong type of 'b' argument, 'str' expected, got 'int' + ''' - """ - # Get the signature; only needs to be calculated once. - sig = Signature(fxn) - def check(*args, **kwargs): - # Find out the variable -> value bindings. - bindings = sig.bind(*args, **kwargs) - # Check *args for the proper quack. + sig = inspect.signature(func) + + types = {} + for param in sig.parameters.values(): + # Iterate through function's parameters and build the list of + # arguments types try: - duck = sig.var_annotations[sig.var_args] - except KeyError: - pass + type_ = param.annotation + except AttributeError: + continue else: - # Check every value in *args. - for value in bindings[sig.var_args]: - if not isinstance(value, duck): - raise TypeError("*%s contains a a value that does not " - "quack like a %r" % - (sig.var_args, duck)) - # Remove it from the bindings so as to not check it again. - del bindings[sig.var_args] - # **kwargs. - try: - duck = sig.var_annotations[sig.var_kw_args] - except (KeyError, AttributeError): - pass - else: - # Check every value in **kwargs. - for value in bindings[sig.var_kw_args].values(): - if not isinstance(value, duck): - raise TypeError("**%s contains a value that does not " - "quack like a %r" % - (sig.var_kw_args, duck)) - # Remove from bindings so as to not check again. - del bindings[sig.var_kw_args] - # For each remaining variable ... - for var, value in bindings.items(): - # See if an annotation was set. + if not inspect.isclass(type_): + # Not a type, skip it + continue + + types[param.name] = type_ + + # If the argument has a type specified, let's check that its + # default value (if present) conforms with the type. try: - duck = sig[var].annotation + default = param.default except AttributeError: continue - # Check that the value quacks like it should. - if not isinstance(value, duck): - raise TypeError('%r does not quack like a %s' % (value, duck)) - else: - # All the ducks quack fine; let the call proceed. - returned = fxn(*args, **kwargs) - # Check the return value. + else: + if not isinstance(default, type_): + raise ValueError("{func}: wrong type of a default value for {arg!r}". \ + format(func=sig.qualname, arg=param.name)) + + def check_type(sig, arg_name, arg_type, arg_value): + # Internal function that incapsulates arguments type checking + if not isinstance(arg_value, arg_type): + raise ValueError("{func}: wrong type of {arg!r} argument, " \ + "{exp!r} expected, got {got!r}". \ + format(func=sig.qualname, arg=arg_name, + exp=arg_type.__name__, got=type(arg_value).__name__)) + + @functools.wraps(func) + def wrapper(*args, **kwargs): + # Let's bind the arguments + ba = sig.bind(*args, **kwargs) + for arg_name, arg in ba.arguments.items(): + # And iterate through the bound arguments try: - if not isinstance(returned, sig.return_annotation): - raise TypeError('the return value %r does not quack like ' - 'a %r' % (returned, - sig.return_annotation)) - except AttributeError: - pass - return returned - # Full-featured version would set function metadata. - return check + type_ = types[arg_name] + except KeyError: + continue + else: + # OK, we have a type for the argument, lets get the corresponding + # parameter description from the signature object + param = sig.parameters[arg_name] + if param.is_args: + # If this parameter is a variable-argument parameter, + # then we need to check each of its values + for value in arg: + check_type(sig, arg_name, type_, value) + elif param.is_kwargs: + # If this parameter is a variable-keyword-argument parameter: + for subname, value in arg.items(): + check_type(sig, arg_name + ':' + subname, type_, value) + else: + # And, finally, if this parameter a regular one: + check_type(sig, arg_name, type_, arg) + + result = func(*ba.args, **ba.kwargs) + # The last bit - let's check that the result is correct + try: + return_type = sig.return_annotation + except AttributeError: + # Looks like we don't have any restriction on the return type + pass + else: + if isinstance(return_type, type) and not isinstance(result, return_type): + raise ValueError('{func}: wrong return type, {exp} expected, got {got}'. \ + format(func=sig.qualname, exp=return_type.__name__, + got=type(result).__name__)) + return result + + return wrapper Open Issues @@ -280,54 +324,23 @@ pass a function object to a function and that would generate the Signature object and store it to ``__signature__`` if needed, and then return the value of ``__signature__``. - -Should ``Signature.bind`` return Parameter objects as keys? ------------------------------------------------------------ - -Instead of returning a dict with keys consisting of the name of the -parameters, would it be more useful to instead use Parameter -objects? The name of the argument can easily be retrieved from the -key (and the name would be used as the hash for a Parameter object). +In the current implementation, signatures are created only on demand +("lazy"). -Have ``var_args`` and ``_var_kw_args`` default to ``None``? ------------------------------------------------------------- +Deprecate ``inspect.getfullargspec()`` and ``inspect.getcallargs()``? +--------------------------------------------------------------------- -It has been suggested by Fred Drake that these two attributes have a -value of ``None`` instead of empty strings when they do not exist. -The answer to this question will influence what the defaults are for -other attributes as well. - - -Deprecate ``inspect.getargspec()`` and ``.formatargspec()``? -------------------------------------------------------------- - -Since the Signature object replicates the use of ``getargspec()`` -from the ``inspect`` module it might make sense to deprecate it in -2.6. ``formatargspec()`` could also go if Signature objects gained a -__str__ representation. - -Issue with that is types such as ``int``, when used as annotations, -do not lend themselves for output (e.g., ``""`` is the -string represenation for ``int``). The repr representation of types -would need to change in order to make this reasonable. - - -Have the objects be "live"? ---------------------------- - -Jim Jewett pointed out that Signature and Parameter objects could be -"live". That would mean requesting information would be done on the -fly instead of caching it on the objects. It would also allow for -mutating the function if the Signature or Parameter objects were -mutated. +Since the Signature object replicates the use of ``getfullargspec()`` +and ``getcallargs()`` from the ``inspect`` module it might make sense +to begin deprecating them in 3.3. References ========== -.. [#impl] pep362 directory in Python's sandbox - (http://svn.python.org/view/sandbox/trunk/pep362/) +.. [#impl] pep362 branch (https://bitbucket.org/1st1/cpython/overview) +.. [#issue] issue 15008 (http://bugs.python.org/issue15008) Copyright @@ -335,7 +348,6 @@ Copyright This document has been placed in the public domain. - .. Local Variables: From 7bac3da1c4753c1709de741f155cde21d8cc0c94 Mon Sep 17 00:00:00 2001 From: Nick Coghlan Date: Wed, 6 Jun 2012 21:40:04 +1000 Subject: [PATCH 06/16] PEP 422 rewrite to present an idea that a) isn't crazy and b) it turns out Thomas Heller proposed back in 2001 --- pep-0422.txt | 345 ++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 242 insertions(+), 103 deletions(-) diff --git a/pep-0422.txt b/pep-0422.txt index fabffdd82..1c22a4bb8 100644 --- a/pep-0422.txt +++ b/pep-0422.txt @@ -1,5 +1,5 @@ PEP: 422 -Title: Dynamic class decorators +Title: Simple class initialisation hook Version: $Revision$ Last-Modified: $Date$ Author: Nick Coghlan @@ -7,148 +7,287 @@ Status: Draft Type: Standards Track Content-Type: text/x-rst Created: 5-Jun-2012 +Python-Version: 3.4 Post-History: 5-Jun-2012 Abstract ======== -Classes currently support two mechanisms for modification of the class at -definition time: metaclasses and lexical decorators. +In Python 2, the body of a class definition could modify the way a class +was created (or simply arrange to run other code after the class was created) +by setting the ``__metaclass__`` attribute in the class body. While doing +this implicitly from called code required the use of an implementation detail +(specifically, ``sys._getframes()``), it could also be done explicitly in a +fully supported fashion (for example, by passing ``locals()`` to an +function that calculated a suitable ``__metaclass__`` value) -Metaclasses can be awkward and challenging to use correctly in conjunction -with multiple inheritance and lexical decorators don't interact with class -inheritance at all. +There is currently no corresponding mechanism in Python 3 that allows the +code executed in the class body to directly influence how the class object +is created. Instead, the class creation process is fully defined by the +class header, before the class body even begins executing. -This PEP proposes a new mechanism for dynamic class decoration that -interacts more cleanly with class inheritance mechanisms. +This PEP proposes a mechanism that will once again allow the body of a +class definition to more directly influence the way a class is created +(albeit in a more constrained fashion), as well as replacing some current +uses of metaclasses with a simpler, easier to understand alternative. -Specification -============= +Background +========== -This PEP proposes that a new step be added to the class creation process, -after the metaclass invocation to construct the class instance and before -the application of lexical decorators. +For an already created class ``cls``, the term "metaclass" has a clear +meaning: it is the value of ``type(cls)``. -This step will walk the class MRO in reverse order, looking for -``__decorators__`` entries in each class dictionary. These entries are -expected to be iterables that are also walked in reverse order to retrieve -class decorators that are automatically applied to the class being defined:: +*During* class creation, it has another meaning: it is also used to refer to +the metaclass hint that may be provided as part of the class definition. +While in many cases these two meanings end up referring to one and the same +object, there are two situations where that is not the case: - for entry in reversed(cls.mro()): - decorators = entry.__dict__.get("__decorators__", ()) - for deco in reversed(decorators): - cls = deco(cls) +* If the metaclass hint refers to an instance of ``type``, then it is + considered as a candidate metaclass along with the metaclasses of all of + the parents of the class being defined. If a more appropriate metaclass is + found amongst the candidates, then it will be used instead of the one + given in the metaclass hint. +* Otherwise, an explicit metaclass hint is assumed to be a factory function + and is called directly to create the class object. In this case, the final + metaclass will be determined by the factory function definition. In the + typical case (where the factory functions just calls ``type``, or, in + Python 3.3 or later, ``types.new_class``) the actual metaclass is then + determined based on the parent classes. -This step in the class creation process will be an implicit part of the -class statement and also part of the behaviour of ``types.new_class()``. +It is notable that only the actual metaclass is inherited - a factory +function used as a metaclass hook sees only the class currently being +defined, and is not invoked for any subclasses. + +In Python 3, the metaclass hint is provided using the ``metaclass=Meta`` +keyword syntax in the class header. This allows the ``__prepare__`` method +on the metaclass to be used to create the ``locals()`` namespace used during +execution of the class body (for example, specifying the use of +``collections.OrderedDict`` instead of a regular ``dict``). + +In Python 2, there was no ``__prepare__`` method (that API was added for +Python 3 by PEP 3115). Instead, a class body could set the ``__metaclass__`` +attribute, and the class creation process would extract that value from the +class namespace to use as the metaclass hint. There is `published code`_ that +makes use of this feature. -Rationale -========= +Proposal +======== -When decorator support was added to classes, the lexical decoration syntax -was copied directly from function decorators:: +This PEP proposes that a mechanism be added to Python 3 that meets the +following criteria: - @decorator - class Example: - # Subclasses will not be decorated automatically - pass +# Restores the ability for class namespaces to have some influence on the + class creation process (above and beyond populating the namespace itself), + but potentially without the full flexibility of the Python 2 style + ``__metaclass__`` hook +# Integrates nicely with class inheritance structures (including mixins and + multiple inheritance) +# Integrates nicely with the implicit ``__class__`` reference and + zero-argument ``super()`` syntax introduced by PEP 3135 +# Can be added to an existing base class without a significant risk of + introducing backwards compatibility problems -This mechanism works well, so long as it is considered acceptable that the -decorator is *not* applied automatically to any subclasses. If it is -desired that the behaviour be inherited, it is currently necessary to -make the step up to defining a `custom metaclass`_:: +One mechanism that would achieve this goal is to add a new class +initialisation hook, modelled directly on the existing instance +initialisation hook. However, the signature would be constrained to ensure +that correctly supporting multiple inheritance is kept as simple as possible. - class DynamicDecorators(type): - """Metaclass for dynamic decorator support +Specifically, it is proposed that class definitions be able to provide a +class initialisation hook as follows:: - Creates the class normally, then runs through the MRO looking for - __decorators__ attributes and applying the contained decorators to - the newly created class - """ - def __new__(meta, name, bases, ns): - cls = super(DynamicDecorators, meta).__new__(meta, name, bases, ns) - for entry in reversed(cls.mro()): - decorators = entry.__dict__.get("__decorators__", ()) - for deco in reversed(decorators): - cls = deco(cls) + class Example: + @classmethod + def __init_class__(cls): + # This is invoked after the class is created, but before any + # explicit decorators are called + # The usual super() mechanisms are used to correctly support + # multiple inheritance. The simple, decorator style invocation + # ensures that this is as simple as possible. + +If present on the created object, this new hook will be called by the class +creation machinery *after* the ``__class__`` reference has been initialised. +For ``types.new_class()``, it will be called as the last step before +returning the created class object. Calling the hook automatically from +``type.__init__`` unfortunately doesn't work, as it would mean the +``__init_class__`` method would be unable to call any methods that relied +on the ``__class__`` reference (or used the zero-argument form of +``super()``). + +If a metaclass wishes to block class initialisation for some reason, it +must arrange for ``cls.__init_class__`` to trigger ``AttributeError``. + +This general proposal is not a new idea (it was first suggested `more than +10 years ago`_), but I believe the situation has changed sufficiently in +that time that the idea is worth reconsidering. + + +Key Benefits +============ + + +Replaces dynamic setting of ``__metaclass__`` +--------------------------------------------- + +For use cases that didn't involve completely replacing the defined class, +Python 2 code that dynamically set ``__metaclass__`` can now dynamically +set ``__init_class__`` instead. For more advanced use cases, introduction of +an explicit metaclass will still be necessary in order to support Python 3. + + +Easier inheritance of definition time behaviour +----------------------------------------------- + +Understanding Python's metaclass system requires a deep understanding of +the type system and the class construction process. This is legitimately +seen as confusing, due to the need to keep multiple moving parts (the code, +the metaclass hint, the actual metaclass, the class object, instances of the +class object) clearly distinct in your mind. + +Understanding the proposed class initialisation hook requires understanding +decorators and ordinary method inheritance, which is a much simpler prospect. + + +Reduced chance of metaclass conflicts +------------------------------------- + +One of the big issues that makes library authors reluctant to use metaclasses +(even when it would be appropriate) is the risk of metaclass conflicts. +These occur whenever two unrelated metaclasses are used by the desired +parents of a class definition. This risk also makes it very difficult to +*add* a metaclass to a class that has previously been published without one. + +By contrast, adding an ``__init_class__`` method to an existing type poses +a similar level of risk to adding an ``__init__`` method: technically, there +is a risk of breaking poorly implemented subclasses, but when that occurs, +it is recognised as a bug in the subclass rather than the library author +breaching backwards compatibility guarantees. In fact, due to the constrained +signature, the risk in this case is actually even lower than in the case of +``__init__``. + + +Integrates cleanly with PEP 3135 +-------------------------------- + +Unlike code that runs as part of the metaclass, code that runs as part of +the new hook will be able to freely invoke class methods that rely on the +implicit ``__class__`` reference introduced by PEP 3135, including methods +that use the zero argument form of ``super()``. + + +Alternatives +============ + + +The Python 3 Status Quo +----------------------- + +The Python 3 status quo already offers a great deal of flexibility. For +changes which only affect a single class definition and which can be +specified at the time the code is written, then class decorators can be +used to modify a class explicitly. Class decorators largely ignore class +inheritance and can make full use of methods that rely on the ``__class__`` +reference being populated. + +Using a custom metaclass provides the same level of power as it did in +Python 2. However, it's notable that, unlike class decorators, a metaclass +cannot call any methods that rely on the ``__class__`` reference, as that +reference is not populated until after the metaclass constructor returns +control to the class creation code. + +One major use case for metaclasses actually closely resembles the use of +class decorators. It occurs whenever a metaclass has an implementation that +uses the following pattern:: + + class Metaclass(type): + def __new__(meta, *args, **kwds): + cls = super(Metaclass, meta).__new__(meta, *args, **kwds) + # Do something with cls return cls - class Example(metaclass=DynamicDecorators): - # Subclasses *will* be decorated automatically - __decorators__ = [decorator] +The key difference between this pattern and a class decorator is that it +is automatically inherited by subclasses. However, it also comes with a +major disadvantage: Python does not allow you to inherit from classes with +unrelated metaclasses. -The main potential problem with this approach, is that it can place -significant constraints on the type heirarchy, as it requires that all -metaclasses used be well behaved with respect to multiple inheritance. +Thus, the status quo requires that developers choose between the following +two alternatives: -By making dynamic decorators an inherent part of the class creation process, -many current use cases of metaclasses may be replaced with dynamic decorators -instead, greatly reducing the likelihood of metaclass conflicts, as well -as being substantially easier to write correctly in the first place. +* Use a class decorator, meaning that behaviour is not inherited and must be + requested explicitly on every subclass +* Use a metaclass, meaning that behaviour is inherited, but metaclass + conflicts may make integration with other libraries and frameworks more + difficult than it otherwise would be + +If this PEP is ultimately rejected, then this is the existing design that +will remain in place by default. -Design Discussion -================= +Restoring the Python 2 metaclass hook +------------------------------------- + +One simple alternative would be to restore support for a Python 2 style +``metaclass`` hook in the class body. This would be checked after the class +body was executed, potentially overwriting the metaclass hint provided in the +class header. + +The main attraction of such an approach is that it would simplify porting +Python 2 applications that make use of this hook (especially those that do +so dynamically). + +However, this approach does nothing to simplify the process of adding +*inherited* class definition time behaviour, nor does it interoperate +cleanly with the PEP 3135 ``__class__`` and ``super()`` semantics (as with +any metaclass based solution, the ``__metaclass__`` hook would have to run +before the ``__class__`` reference has been populated. -Allowing metaclasses to override the dynamic decoration process ---------------------------------------------------------------- +Dynamic class decorators +------------------------ -This PEP does not provide a mechanism that allows metaclasses to override the -dynamic decoration process. If this feature is deemed desirable in the -future, then it can be added by moving the functionality described in -this PEP into a new method on the metaclass (for example, ``__decorate__``), -with ``type`` providing a suitable default implementation that matches -the behaviour described here. +The original version of this PEP was called "Dynamic class decorators" and +focused solely on a significantly more complicated proposal than that +presented in the current version. -This PEP chose the simplicity of the current approach, as lexical decorators -are currently outside the scope of metaclass control, so it seems reasonable -to pursue the simpler strategy in the absence of a solid use case for -making this behaviour configurable. +As with the current version, it proposed that a new step be added to the +class creation process, after the metaclass invocation to construct the +class instance and before the application of lexical decorators. However, +instead of a simple process of calling a single class method that relies +on normal inheritance mechanisms, it proposed a far more complicated +procedure that walked the class MRO looking for decorators stored in +iterable ``__decorators__`` attributes. +Using the current version of the PEP, the scheme originally proposed could +be implemented as:: -Iterating over decorator entries in reverse order -------------------------------------------------- + class DynamicDecorators: + @classmethod + def __init_class__(cls): + super(DynamicDecorators, cls).__init_class__() + for entry in reversed(cls.mro()): + decorators = entry.__dict__.get("__decorators__", ()) + for deco in reversed(decorators): + cls = deco(cls) -This order was chosen to match the layout of lexical decorators when -converted to ordinary function calls. Just as the following are equivalent:: +Any subclasses of this type would automatically have the contents of any +``__decorators__`` attributes processed and invoked. - @deco2 - @deco1 - class C: - pass +The mechanism in the current PEP is considered superior, as many issues +to do with ordering and the same decorator being invoked multiple times +simple go away, as that kind of thing is taken care of through the use of an +ordinary class method invocation. - class C: - pass - C = deco2(deco1(C)) - -So too will the following be roughly equivalent (aside from inheritance):: - - class C: - __decorators__ = [deco2, deco1] - - class C: - pass - C = deco2(deco1(C)) - - -Iterating over the MRO in reverse order ---------------------------------------- - -The order of iteration over the MRO for decorator application was chosen to -match the order of actual call *evaluation* when using ``super`` to invoke -parent class implementations: the first method to run to completion is that -closest to the base of the class hierarchy. - References ========== -.. _custom metaclass: - https://bitbucket.org/ncoghlan/misc/src/default/pep422.py +.. _published code: + http://mail.python.org/pipermail/python-dev/2012-June/119878.html + +.. _more than 10 years ago: + http://mail.python.org/pipermail/python-dev/2001-November/018651.html Copyright From 2e9b04a22aa247c0f5a7543607dd0002a07f05f2 Mon Sep 17 00:00:00 2001 From: Nick Coghlan Date: Wed, 6 Jun 2012 21:44:01 +1000 Subject: [PATCH 07/16] Fix numbered bullet points --- pep-0422.txt | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/pep-0422.txt b/pep-0422.txt index 1c22a4bb8..8cd370ee2 100644 --- a/pep-0422.txt +++ b/pep-0422.txt @@ -79,16 +79,16 @@ Proposal This PEP proposes that a mechanism be added to Python 3 that meets the following criteria: -# Restores the ability for class namespaces to have some influence on the - class creation process (above and beyond populating the namespace itself), - but potentially without the full flexibility of the Python 2 style - ``__metaclass__`` hook -# Integrates nicely with class inheritance structures (including mixins and - multiple inheritance) -# Integrates nicely with the implicit ``__class__`` reference and - zero-argument ``super()`` syntax introduced by PEP 3135 -# Can be added to an existing base class without a significant risk of - introducing backwards compatibility problems +1. Restores the ability for class namespaces to have some influence on the + class creation process (above and beyond populating the namespace itself), + but potentially without the full flexibility of the Python 2 style + ``__metaclass__`` hook +2. Integrates nicely with class inheritance structures (including mixins and + multiple inheritance) +3. Integrates nicely with the implicit ``__class__`` reference and + zero-argument ``super()`` syntax introduced by PEP 3135 +4. Can be added to an existing base class without a significant risk of + introducing backwards compatibility problems One mechanism that would achieve this goal is to add a new class initialisation hook, modelled directly on the existing instance From acc053fc607684b2fdcf052a00f68f271d24e955 Mon Sep 17 00:00:00 2001 From: Nick Coghlan Date: Wed, 6 Jun 2012 21:45:26 +1000 Subject: [PATCH 08/16] Reword a confusing sentence --- pep-0422.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pep-0422.txt b/pep-0422.txt index 8cd370ee2..33b225242 100644 --- a/pep-0422.txt +++ b/pep-0422.txt @@ -104,8 +104,8 @@ class initialisation hook as follows:: # This is invoked after the class is created, but before any # explicit decorators are called # The usual super() mechanisms are used to correctly support - # multiple inheritance. The simple, decorator style invocation - # ensures that this is as simple as possible. + # multiple inheritance. The decorator style invocation helps + # ensure that invoking the parent class is as simple as possible. If present on the created object, this new hook will be called by the class creation machinery *after* the ``__class__`` reference has been initialised. From 59de0218af2a029ce75557bd78879666ffe7f3b3 Mon Sep 17 00:00:00 2001 From: Nick Coghlan Date: Wed, 6 Jun 2012 21:49:50 +1000 Subject: [PATCH 09/16] Eliminate a typo. Also wonder how many times I can use the word 'simple' or a derivative in one PEP. --- pep-0422.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pep-0422.txt b/pep-0422.txt index 33b225242..f6c85ed19 100644 --- a/pep-0422.txt +++ b/pep-0422.txt @@ -276,7 +276,7 @@ Any subclasses of this type would automatically have the contents of any The mechanism in the current PEP is considered superior, as many issues to do with ordering and the same decorator being invoked multiple times -simple go away, as that kind of thing is taken care of through the use of an +just go away, as that kind of thing is taken care of through the use of an ordinary class method invocation. From fa7af10a993779e77e556c273e8bcd38886756f1 Mon Sep 17 00:00:00 2001 From: Nick Coghlan Date: Thu, 7 Jun 2012 22:08:41 +1000 Subject: [PATCH 10/16] Update 422 based on python-dev feedback --- pep-0422.txt | 99 ++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 73 insertions(+), 26 deletions(-) diff --git a/pep-0422.txt b/pep-0422.txt index f6c85ed19..e564eed6f 100644 --- a/pep-0422.txt +++ b/pep-0422.txt @@ -44,7 +44,7 @@ the metaclass hint that may be provided as part of the class definition. While in many cases these two meanings end up referring to one and the same object, there are two situations where that is not the case: -* If the metaclass hint refers to an instance of ``type``, then it is +* If the metaclass hint refers to a subclass of ``type``, then it is considered as a candidate metaclass along with the metaclasses of all of the parents of the class being defined. If a more appropriate metaclass is found amongst the candidates, then it will be used instead of the one @@ -72,6 +72,16 @@ attribute, and the class creation process would extract that value from the class namespace to use as the metaclass hint. There is `published code`_ that makes use of this feature. +Another new feature in Python 3 is the zero-argument form of the ``super()`` +builtin, introduced by PEP 3135. This feature uses an implicit ``__class__`` +reference to the class being defined to replace the "by name" references +required in Python 2. Just as code invoked during execution of a Python 2 +metaclass could not call methods that referenced the class by name (as the +name had not yet been bound in the containing scope), similarly, Python 3 +metaclasses cannot call methods that rely on the implicit ``__class__`` +reference (as it is not populated until after the metaclass has returned +control to the class creation machiner). + Proposal ======== @@ -90,10 +100,10 @@ following criteria: 4. Can be added to an existing base class without a significant risk of introducing backwards compatibility problems -One mechanism that would achieve this goal is to add a new class +One mechanism that can achieve this goal is to add a new class initialisation hook, modelled directly on the existing instance -initialisation hook. However, the signature would be constrained to ensure -that correctly supporting multiple inheritance is kept as simple as possible. +initialisation hook, but with the signature constrained to match that +of an ordinary class decorator. Specifically, it is proposed that class definitions be able to provide a class initialisation hook as follows:: @@ -110,51 +120,57 @@ class initialisation hook as follows:: If present on the created object, this new hook will be called by the class creation machinery *after* the ``__class__`` reference has been initialised. For ``types.new_class()``, it will be called as the last step before -returning the created class object. Calling the hook automatically from -``type.__init__`` unfortunately doesn't work, as it would mean the -``__init_class__`` method would be unable to call any methods that relied -on the ``__class__`` reference (or used the zero-argument form of -``super()``). +returning the created class object. If a metaclass wishes to block class initialisation for some reason, it must arrange for ``cls.__init_class__`` to trigger ``AttributeError``. -This general proposal is not a new idea (it was first suggested `more than -10 years ago`_), but I believe the situation has changed sufficiently in -that time that the idea is worth reconsidering. +This general proposal is not a new idea (it was first suggested for +inclusion in the language definition `more than 10 years ago`_, and a +similar mechanism has long been supported by `Zope's ExtensionClass`_), +but I believe the situation has changed sufficiently in recent years that +the idea is worth reconsidering. Key Benefits ============ -Replaces dynamic setting of ``__metaclass__`` ---------------------------------------------- +Replaces many use cases for dynamic setting of ``__metaclass__`` +----------------------------------------------------------------- -For use cases that didn't involve completely replacing the defined class, +For use cases that don't involve completely replacing the defined class, Python 2 code that dynamically set ``__metaclass__`` can now dynamically set ``__init_class__`` instead. For more advanced use cases, introduction of -an explicit metaclass will still be necessary in order to support Python 3. +an explicit metaclass (possibly made available as a required base class) will +still be necessary in order to support Python 3. Easier inheritance of definition time behaviour ----------------------------------------------- -Understanding Python's metaclass system requires a deep understanding of +Understanding Python's metaclasses requires a deep understanding of the type system and the class construction process. This is legitimately -seen as confusing, due to the need to keep multiple moving parts (the code, +seen as challenging, due to the need to keep multiple moving parts (the code, the metaclass hint, the actual metaclass, the class object, instances of the -class object) clearly distinct in your mind. +class object) clearly distinct in your mind. Even when you know the rules, +it's still easy to make a mistake if you're not being extremely careful. +An earlier version of this PEP actually included such a mistake: it +stated "instance of type" for a constraint that is actually "subclass of +type". -Understanding the proposed class initialisation hook requires understanding -decorators and ordinary method inheritance, which is a much simpler prospect. +Understanding the proposed class initialisation hook only requires +understanding decorators and ordinary method inheritance, which isn't +quite as daunting a task. The new hook provides a more gradual path +towards understanding all of the phases involved in the class definition +process. Reduced chance of metaclass conflicts ------------------------------------- One of the big issues that makes library authors reluctant to use metaclasses -(even when it would be appropriate) is the risk of metaclass conflicts. +(even when they would be appropriate) is the risk of metaclass conflicts. These occur whenever two unrelated metaclasses are used by the desired parents of a class definition. This risk also makes it very difficult to *add* a metaclass to a class that has previously been published without one. @@ -164,12 +180,12 @@ a similar level of risk to adding an ``__init__`` method: technically, there is a risk of breaking poorly implemented subclasses, but when that occurs, it is recognised as a bug in the subclass rather than the library author breaching backwards compatibility guarantees. In fact, due to the constrained -signature, the risk in this case is actually even lower than in the case of -``__init__``. +signature of ``__init_class__``, the risk in this case is actually even +lower than in the case of ``__init__``. -Integrates cleanly with PEP 3135 --------------------------------- +Integrates cleanly with \PEP 3135 +--------------------------------- Unlike code that runs as part of the metaclass, code that runs as part of the new hook will be able to freely invoke class methods that rely on the @@ -280,6 +296,35 @@ just go away, as that kind of thing is taken care of through the use of an ordinary class method invocation. +Automatic metaclass derivation +------------------------------ + +When no appropriate metaclass is found, it's theoretically possible to +automatically derive a metaclass for a new type based on the metaclass hint +and the metaclasses of the bases. + +While adding such a mechanism would reduce the risk of spurious metaclass +conflicts, it would do nothing to improve integration with PEP 3135, would +not help with porting Python 2 code that set ``__metaclass__`` dynamically +and would not provide a more straightforward inherited mechanism for invoking +additional operations after the class invocation is complete. + +In addition, there would still be a risk of metaclass conflicts in cases +where the base metaclasses were not written with multiple inheritance in +mind. In such situations, there's a chance of introducing latent defects +if one or more metaclasses are not invoked correctly. + + +Calling the new hook from ``type.__init__`` +------------------------------------------- + +Calling the new hook automatically from ``type.__init__``, would achieve most +of the goals of this PEP. However, using that approach would mean that +``__init_class__`` implementations would be unable to call any methods that +relied on the ``__class__`` reference (or used the zero-argument form of +``super()``), and could not make use of those features themselves. + + References ========== @@ -289,6 +334,8 @@ References .. _more than 10 years ago: http://mail.python.org/pipermail/python-dev/2001-November/018651.html +.. _Zope's ExtensionClass: + http://docs.zope.org/zope_secrets/extensionclass.html Copyright ========= From e843de4699f3f7c479bb16a0a47151c7da7a7533 Mon Sep 17 00:00:00 2001 From: Brett Cannon Date: Thu, 7 Jun 2012 10:18:08 -0400 Subject: [PATCH 11/16] Update from Yury for PEP 362. --- pep-0362.txt | 99 +++++++++++++++++++++++++++++++--------------------- 1 file changed, 60 insertions(+), 39 deletions(-) diff --git a/pep-0362.txt b/pep-0362.txt index 545b01a35..d30f5bc5b 100644 --- a/pep-0362.txt +++ b/pep-0362.txt @@ -55,10 +55,8 @@ 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. - -Once a Signature object is created for a particular function, -it's cached in the ``__signature__`` attribute of that function. + parameters. Raises a ``BindError`` if the passed arguments + do not match the signature. Changes to the Signature object, or to any of its data members, do not affect the function itself. @@ -86,19 +84,25 @@ The structure of the Parameter object is: True if the parameter is keyword-only, else False. * is_args : bool True if the parameter accepts variable number of arguments - (``\*args``-like), else False. + (``*args``-like), else False. * is_kwargs : bool True if the parameter accepts variable number of keyword - arguments (``\*\*kwargs``-like), else False. + arguments (``**kwargs``-like), else False. * is_implemented : bool True if the parameter is implemented for use. Some platforms implement functions but can't support specific parameters - (e.g. "mode" for os.mkdir). Passing in an unimplemented + (e.g. "mode" for ``os.mkdir``). Passing in an unimplemented parameter may result in the parameter being ignored, or in NotImplementedError being raised. It is intended that 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``. + BoundArguments Object ===================== @@ -135,16 +139,58 @@ The ``arguments`` attribute should be used in conjunction with Implementation ============== +The implementation adds a new function ``signature()`` to the ``inspect`` +module. The function is the preferred way of getting a ``Signature`` for +a callable object. + +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 + + - If it is ``None`` and the object is an instance of + ``BuiltinFunction``, raise a ``ValueError`` + + - If the object is a an instance of ``FunctionType``: + + - If it has a ``__wrapped__`` attribute, return + ``signature(object.__wrapped__)`` + + - Or else construct a new ``Signature`` object and return it + + - if the object is a method, construct and return a new ``Signature`` + object, with its first parameter (usually ``self``) removed + + - If the object is a class return ``signature(object.__init__)`` + + - Return ``signature(object.__call__)`` + +Note, that the ``Signature`` object is created in a lazy manner, and +is not automatically cached. + An implementation for Python 3.3 can be found here: [#impl]_. A python issue was also created: [#issue]_. -The implementation adds a new function ``signature()`` to the -``inspect`` module. ``signature()`` returns the value stored -on the ``__signature__`` attribute if it exists, otherwise it -creates the Signature object for the function and caches it in -the function's ``__signature__``. (For methods this is stored -directly in the ``__func__`` function object, since that is what -decorators work with.) + +Design Considerations +===================== + +No Implicit Caching of Signature Objects +---------------------------------------- + +The first PEP design had a provision for implicit caching of ``Signature`` +objects in the ``inspect.signature()`` function. However, this has the +following downsides: + + * If the ``Signature`` object is cached then any changes to the function + it describes will not be reflected in it. However, If the caching is + needed, it can be always done manually and explicitly + + * It is better to reserve the ``__signature__`` attribute for the cases + when there is a need to explicitly set to a ``Signature`` object that + is different from the actual one Examples @@ -311,31 +357,6 @@ Annotation Checker return wrapper -Open Issues -=========== - -When to construct the Signature object? ---------------------------------------- - -The Signature object can either be created in an eager or lazy -fashion. In the eager situation, the object can be created during -creation of the function object. In the lazy situation, one would -pass a function object to a function and that would generate the -Signature object and store it to ``__signature__`` if -needed, and then return the value of ``__signature__``. - -In the current implementation, signatures are created only on demand -("lazy"). - - -Deprecate ``inspect.getfullargspec()`` and ``inspect.getcallargs()``? ---------------------------------------------------------------------- - -Since the Signature object replicates the use of ``getfullargspec()`` -and ``getcallargs()`` from the ``inspect`` module it might make sense -to begin deprecating them in 3.3. - - References ========== From a0f82b88873791a9344b90940ade843444de89ab Mon Sep 17 00:00:00 2001 From: Brett Cannon Date: Thu, 7 Jun 2012 10:25:42 -0400 Subject: [PATCH 12/16] Another update from Yury for PEP 362. --- pep-0362.txt | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/pep-0362.txt b/pep-0362.txt index d30f5bc5b..12dd71784 100644 --- a/pep-0362.txt +++ b/pep-0362.txt @@ -160,11 +160,16 @@ The function implements the following algorithm: - Or else construct a new ``Signature`` object and return it - - if the object is a method, construct and return a new ``Signature`` - object, with its first parameter (usually ``self``) removed + - 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 an instance of ``functools.partial``, construct + a new ``Signature`` from its ``partial.func`` attribute, and + account for already bound ``partial.args`` and ``partial.kwargs`` + - Return ``signature(object.__call__)`` Note, that the ``Signature`` object is created in a lazy manner, and From e36d9f7b4980baadfbd17efa1fe00d9cb4a228f6 Mon Sep 17 00:00:00 2001 From: Brett Cannon Date: Wed, 13 Jun 2012 22:15:01 -0400 Subject: [PATCH 13/16] 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 From e22d1112c4125b6ee09d0710a93f9911b8ef5eec Mon Sep 17 00:00:00 2001 From: Brett Cannon Date: Thu, 14 Jun 2012 10:10:28 -0400 Subject: [PATCH 14/16] Fix a spelling error. --- pep-0362.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pep-0362.txt b/pep-0362.txt index 9235e252c..34d523f47 100644 --- a/pep-0362.txt +++ b/pep-0362.txt @@ -320,7 +320,7 @@ Annotation Checker format(func=sig.qualname, arg=param.name)) def check_type(sig, arg_name, arg_type, arg_value): - # Internal function that incapsulates arguments type checking + # Internal function that encapsulates arguments type checking if not isinstance(arg_value, arg_type): raise ValueError("{func}: wrong type of {arg!r} argument, " \ "{exp!r} expected, got {got!r}". \ From 5f54b726b599a8e35c0d47faa2390c0916cee743 Mon Sep 17 00:00:00 2001 From: Brett Cannon Date: Fri, 15 Jun 2012 13:56:20 -0400 Subject: [PATCH 15/16] Update from Yury. --- pep-0362.txt | 164 +++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 126 insertions(+), 38 deletions(-) diff --git a/pep-0362.txt b/pep-0362.txt index 34d523f47..fcef361f8 100644 --- a/pep-0362.txt +++ b/pep-0362.txt @@ -51,12 +51,13 @@ 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`` (subclass of ``TypeError``) - if the passed arguments do not match the signature. + parameters. Raises a ``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.) + behavior.) Raises a ``TypeError`` if the passed arguments do + not match the signature. * format(...) -> str Formats the Signature object to a string. Optional arguments allow for custom render functions for parameter names, @@ -84,27 +85,53 @@ 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 parameter has no default value, this attribute is not set. + * annotation : object The annotation for the parameter if specified. If the parameter has no annotation, this attribute is not set. -* is_keyword_only : bool - True if the parameter is keyword-only, else False. -* is_args : bool - True if the parameter accepts variable number of arguments - (``*args``-like), else False. -* is_kwargs : bool - True if the parameter accepts variable number of keyword - arguments (``**kwargs``-like), else False. -* is_implemented : bool + +* kind : str + Describes how argument values are bound to the parameter. + Possible values: + + * ``Parameter.POSITIONAL_ONLY`` - value must be supplied + as a positional argument. + + Python has no explicit syntax for defining positional-only + parameters, but many builtin and extension module functions + (especially those that accept only one or two parameters) + accept them. + + * ``Parameter.POSITIONAL_OR_KEYWORD`` - value may be + supplied as either a keyword or positional argument + (this is the standard binding behaviour for functions + implemented in Python.) + + * ``Parameter.KEYWORD_ONLY`` - value must be supplied + as a keyword argument. Keyword only parameters are those + which appear after a "*" or "\*args" entry in a Python + function definition. + + * ``Parameter.VAR_POSITIONAL`` - a tuple of positional + arguments that aren't bound to any other parameter. + This corresponds to a "\*args" parameter in a Python + function definition. + + * ``Parameter.VAR_KEYWORD`` - a dict of keyword arguments + that aren't bound to any other parameter. This corresponds + to a "\*\*kwds" parameter in a Python function definition. + +* implemented : bool True if the parameter is implemented for use. Some platforms implement functions but can't support specific parameters (e.g. "mode" for ``os.mkdir``). Passing in an unimplemented parameter may result in the parameter being ignored, or in NotImplementedError being raised. It is intended that - all conditions where ``is_implemented`` may be False be + all conditions where ``implemented`` may be False be thoroughly documented. Two parameters are equal when all their attributes are equal. @@ -159,12 +186,11 @@ The function implements the following algorithm: - If it is ``None`` and the object is an instance of ``BuiltinFunction``, raise a ``ValueError`` - - If the object is a an instance of ``FunctionType``: + - If it has a ``__wrapped__`` attribute, return + ``signature(object.__wrapped__)`` - - If it has a ``__wrapped__`` attribute, return - ``signature(object.__wrapped__)`` - - - Or else construct a new ``Signature`` object and return it + - If the object is a an instance of ``FunctionType`` construct + and return a new ``Signature`` for it - If the object is a method or a classmethod, construct and return a new ``Signature`` object, with its first parameter (usually @@ -223,6 +249,9 @@ Examples Visualizing Callable Objects' Signature --------------------------------------- + +Let's define some classes and functions: + :: from inspect import signature @@ -245,25 +274,62 @@ Visualizing Callable Objects' Signature return a, b, c - 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)))) + def shared_vars(*shared_args): + """Decorator factory that defines shared variables that are + passed to every invocation of the function""" + + def decorator(f): + @wraps(f) + def wrapper(*args, **kwds): + full_args = shared_args + args + return f(*full_args, **kwds) + # Override signature + sig = wrapper.__signature__ = signature(f) + for __ in shared_args: + sig.parameters.popitem(last=False) + return wrapper + return decorator -The script will output: + @shared_vars({}) + def example(_state, a, b, c): + return _state, a, b, c + + + def format_signature(obj): + return str(signature(obj)) + + +Now, in the python REPL: + :: - 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 + >>> format_signature(FooMeta) + '(name, bases, dct, *, bar:bool=False)' + + >>> format_signature(Foo) + '(spam:int=42)' + + >>> format_signature(Foo.__call__) + '(self, a, b, *, c) -> tuple' + + >>> format_signature(Foo().__call__) + '(a, b, *, c) -> tuple' + + >>> format_signature(partial(Foo().__call__, 1, c=3)) + '(b, *, c=3) -> tuple' + + >>> format_signature(partial(partial(Foo().__call__, 1, c=3), 2, c=20)) + '(*, c=20) -> tuple' + + >>> format_signature(example) + '(a, b, c)' + + >>> format_signature(partial(example, 1, 2)) + '(c)' + + >>> format_signature(partial(partial(example, 1, b=2), c=3)) + '(b=2, c=3)' Annotation Checker @@ -317,14 +383,14 @@ Annotation Checker else: if not isinstance(default, type_): raise ValueError("{func}: wrong type of a default value for {arg!r}". \ - format(func=sig.qualname, arg=param.name)) + format(func=func.__qualname__, arg=param.name)) def check_type(sig, arg_name, arg_type, arg_value): # Internal function that encapsulates arguments type checking if not isinstance(arg_value, arg_type): raise ValueError("{func}: wrong type of {arg!r} argument, " \ "{exp!r} expected, got {got!r}". \ - format(func=sig.qualname, arg=arg_name, + format(func=func.__qualname__, arg=arg_name, exp=arg_type.__name__, got=type(arg_value).__name__)) @functools.wraps(func) @@ -341,12 +407,12 @@ Annotation Checker # OK, we have a type for the argument, lets get the corresponding # parameter description from the signature object param = sig.parameters[arg_name] - if param.is_args: + if param.kind == param.VAR_POSITIONAL: # If this parameter is a variable-argument parameter, # then we need to check each of its values for value in arg: check_type(sig, arg_name, type_, value) - elif param.is_kwargs: + elif param.kind == param.VAR_KEYWORD: # If this parameter is a variable-keyword-argument parameter: for subname, value in arg.items(): check_type(sig, arg_name + ':' + subname, type_, value) @@ -364,13 +430,35 @@ Annotation Checker else: if isinstance(return_type, type) and not isinstance(result, return_type): raise ValueError('{func}: wrong return type, {exp} expected, got {got}'. \ - format(func=sig.qualname, exp=return_type.__name__, + format(func=func.__qualname__, exp=return_type.__name__, got=type(result).__name__)) return result return wrapper +Render Function Signature to HTML +--------------------------------- + +:: + + import inspect + + def format_to_html(func): + sig = inspect.signature(func) + + html = sig.format(token_params_separator=',', + token_colon=':', + token_eq='=', + token_return_annotation='->', + token_left_paren='(', + token_right_paren=')', + token_kwonly_separator='*', + format_name=lambda name: ''+name+'') + + return '{}'.format(html) + + References ========== From cfc6f4fc856cb924bfd97503bef46600afc2f1da Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Sat, 16 Jun 2012 23:22:21 +0200 Subject: [PATCH 16/16] PEP update for 3.3. --- pep-0398.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pep-0398.txt b/pep-0398.txt index 33bf3b06a..fc1310811 100644 --- a/pep-0398.txt +++ b/pep-0398.txt @@ -70,6 +70,7 @@ Implemented / Final PEPs: * PEP 417: Including mock in the Standard Library * PEP 418: Add monotonic time, performance counter, and process time functions * PEP 420: Implicit Namespace Packages +* PEP 421: Adding sys.implementation * PEP 3118: Revising the buffer protocol (protocol semantics finalised) * PEP 3144: IP Address manipulation library * PEP 3151: Reworking the OS and IO exception hierarchy @@ -87,8 +88,6 @@ Candidate PEPs: * PEP 362: Function Signature Object * PEP 397: Python launcher for Windows -* PEP 421: Adding sys.implementation -* PEP 3143: Standard daemon process library * PEP 3154: Pickle protocol version 4 (Note that these are not accepted yet and even if they are, they might @@ -105,6 +104,7 @@ Other planned large-scale changes: Deferred to post-3.3: * PEP 395: Qualified Names for Modules +* PEP 3143: Standard daemon process library * Breaking out standard library and docs in separate repos Copyright