[Python-3000] rough draft signature PEP (original) (raw)
Brett Cannon brett at python.org
Sat Apr 22 23:47:58 CEST 2006
- Previous message: [Python-3000] Minor hitch writing the Function Signature PEP
- Next message: [Python-3000] rough draft signature PEP
- Messages sorted by: [ date ] [ thread ] [ subject ] [ author ]
[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: boolImplementation
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
- Previous message: [Python-3000] Minor hitch writing the Function Signature PEP
- Next message: [Python-3000] rough draft signature PEP
- Messages sorted by: [ date ] [ thread ] [ subject ] [ author ]