[Python-3000] rough draft signature PEP (original) (raw)

Brett Cannon brett at python.org
Sat Apr 22 23:47:58 CEST 2006


[I am posting to python-3000 since this is where the parameter list ideas are being discussed, but this is probably generic enough to eventually make it into the 2.x line]

Here is a rough draft of a PEP I wrote last summer after I had Guido come for lunch at Google when I was interning there (i.e., a little old =) . Someone (I think Philip) had suggested something like this back then but it didn't go any farther. I liked the idea and I asked Guido at lunch if he was okay for it; he was.

So I wrote the following PEP along with a Python implementation (attached, along with test cases). It is still a rough draft since I am on vacation on top of visiting my dad for his birthday and thus cannot put a ton of time into this at the moment, so don't consider this a final version in any way. There is already a list of things to consider and I am sure there are some things discussed during the new parameter list ideas that could stand to be worked in. I am quite happy to work on finishing this PEP so that Talin can focus on the parameter list PEP. So just comment away and I will work on incorporating them as time permits.

-Brett


PEP: XXX Title: Introducing the signature Attribute Version: Revision:1.5Revision: 1.5 Revision:1.5 Last-Modified: Date:2005/06/0713:17:37Date: 2005/06/07 13:17:37 Date:2005/06/0713:17:37 Author: Brett Cannon <brett at python.org> Status: Draft Type: Standards Track Content-Type: text/x-rst Created: XX-XXX-XXXX Post-History:

XXX * Break abstract into Abstract and Rationale * write signature() function - maybe not in built-ins; inspect instead? * look up object identity (issue|crisis) as reference * automatically apply signature when decorator called? * make more general by having an attribute that points to original object being wrapped that introspection checks first? - wrapping - identity - introspect - metadata

Abstract

Decorators were introduced to Python in version 2.4 . Their introduction provided a syntactically easy way to "decorate" functions and methods. The ease of use has allowed the use of functions that wrap other functions and methods to become more prevalent in Python code.

Unfortunately, one side-effect of the increased use of decorators is the loss of introspection on the function being wrapped. Most decorators are not meant to be directly noticed by someone using the code; decorators are meant to be heard, not seen. But when a decorator is used on a function or method that returns a new function that wraps the original, introspection will occur on the wrapping function, not the wrapped one.

:: def yell(func): def wrapper(*args, **kwargs): print "Hi!" return func(*args, **kwargs) return wrapper

@yell def wrapped_one_arg(x): pass

def unwrapped_one_arg(x): pass

if name == 'main': from inspect import getargspec

 print getargspec(wrapped_one_arg) == getargspec(unwrapped_one_arg)

To help deal with this phenomenon, the signature attribute is being proposed. It is to be an optional attribute on objects where introspection of function parameters may be performed. It contains an instance of the Signature class that represents all relevant data that one might want to know about the call signature of a function or method. This allows one to assign to a wrapping function's signature attribute an instance of the Signature class that represents the wrapped function or method, thus allowing introspection on the wrapping function to reflect the call signature of the wrapped function. The Signature also works for classes (representing the call signature for instantiation) and instances (for the __call__ method of an instance).

A built-in, aptly named signature, is also being proposed. When passed an object that meets the proper requirements, it will either return a new instance of the Signature class or the pre-existing object for the object passed in.

Types of Argument Parameters

Python has a fairly rich set of function parameter possibilities. To start, there are required arguments; def positional(x): pass. These types of parameters require an argument be passed in for them.

Next, there are default arguments; def defaults(x=42): pass. They have a default value assigned to them if no argument is passed in for that parameter. They are optional, though, unlike required arguments.

Lastly, there are excess arguments; def excess(*args, **kwargs): pass. There are two types of excess arguments. One type is an excess positional argument (*args). When this type of parameter is present all positional arguments provided during the call are collected into a tuple and assigned to the excess positional argument. The other type of parameter is an excess keyword argument. All keyword arguments that are not directly assigned to an existing keyword parameter are collected into a dict keyed on the keyword name and containing a value of the argument passed in. This dict then gets assigned to the excess keyword argument parameter. For both types, direct assignment to the parameter is not permitted.

Signature Object

All types of function parameters mentioned in the section Types of Argument Parameters_ must be handled by the Signature object in order for it to be useful. One should be able to view the Signature object as providing enough information to be able to know what is possible in terms of calling a function or method. That said, there must be a way to handle required arguments, default arguments, and both types of excess argument parameters.

For required arguments, the attribute required_args provides a tuple of strings that correspond to the names of the required arguments in the order they are defined in the function. By providing the name and order one can easily find out the calling convention required for the minimal call of the function or method.

Default arguments are represented by the default_args attribute. A tuple is contained within the attribute which itself contains two item tuples of (name, value) pairs representing the name of the default argument parameter and the value that is given. For instance, for the function def defaults(x=42, y=True): pass, the value held by default_args would be (('x', 42), ('y', True)).

Excess argument parameters each have their own attribute to represent their existence. For the existence of an excess positional argument, excess_pos_args exists. excess_kw_args represents an excess keyword argument parameter. Both attributes contain a boolean value representing whether they parameter exists. While changing the attributes to contain the name of the respective excess parameter is technically feasible, it has been deemed unnecessary since that knowledge is not useful when making an actual call to the function or method that is represented by Signature object.

One key decision that was made during the development of attribute names is taking into consideration the possibility of optional type checking for function parameters. This meant the names should be general enough to represent what they do without type information.

Finally, the Signature object implements a __str__ method. Calling this will return a string representing the parameter as might be seen in the actual code. This is provided for human consumption to easily deduce what is required for calling the object. No parentheses are put around the returned string.

The final interface, following the syntax outlined in [#interface-syntax]_, is::

interface Signature: """Specifies the call signature of a class, instance, method, or class"""

 def __str__(self) -> str:
     """String representation of the parameters"""

 required_args: tuple(str)
 default_args: tuple(tuple(str, object))
 excess_pos_args: bool
 excess_kw_args: bool

Implementation

XXX Signature object (using 'inspect'), changing pydoc (and thus help()), builtin

Example Usage

For all examples, assume the variable sig contains an instance of the Signature object.

Making a decorator have its signature set to the function it is wrapping

:: def wrapper(func): def inner(*args, **kwargs): # Magic happens here ... return func(*args, **kwargs) inner.signature = signature(func) return inner

All positional arguments

:: sig.required_args + (name for name, value in sig.default_args)

Dictionary of default arguments

:: dict(sig.default_args)

References

.. [#interface-syntax] Guido van Rossum's blog ("Interfaces or Abstract Base Classes?") http://www.artima.com/weblogs/viewpost.jsp?thread=92662

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 End: -------------- next part -------------- A non-text attachment was scrubbed... Name: signature.py Type: application/octet-stream Size: 2262 bytes Desc: not available Url : http://mail.python.org/pipermail/python-3000/attachments/20060422/726eb5a2/attachment-0002.obj -------------- next part -------------- A non-text attachment was scrubbed... Name: test_signature.py Type: application/octet-stream Size: 3969 bytes Desc: not available Url : http://mail.python.org/pipermail/python-3000/attachments/20060422/726eb5a2/attachment-0003.obj



More information about the Python-3000 mailing list