[Python-Dev] Updated PEP 362 (Function Signature Object)

Steven D'Aprano steve at pearwood.info
Wed Jun 6 17:38:13 CEST 2012


Brett Cannon wrote:

> PEP: 362
> Title: Function Signature Object
> Version: $Revision$
> Last-Modified: $Date$
> Author: Brett Cannon <brett at python.org>, Jiwon Seo <seojiwon at gmail.com>,
>         Yury Selivanov <yselivanov at sprymix.com>, Larry Hastings <
> larry at hastings.org>
> Status: Draft
> Type: Standards Track
> Content-Type: text/x-rst
> Created: 21-Aug-2006
> Python-Version: 3.3
> Post-History: 04-Jun-2012
> 
> 
> Abstract
> ========
> 
> Python has always supported powerful introspection capabilities,
> 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 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.

It's already easy and straightforward, thanks to the existing 
inspect.getfullargspec function. If this existing inspect function is lacking 
something, the PEP should explain what, and why the inspect function can't be 
fixed.


> 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.

What happens when the existing function metadata and the __signature__ object 
disagree?

Are there use-cases where we want them to disagree, or is disagreement always 
a sign that something is broken?



> 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.

There's a considerable amount of data recorded, including a number of mappings 
(dicts?). This potentially increase the size of functions, and the overhead of 
creating them. Since most functions are never introspected, or only rarely 
introspected, it seems rather wasteful to record all this data "just in case", 
particularly since it's already recorded once in the function metadata and/or 
code object.



> A Signature object has the following public attributes and methods:
> 
> * name : str
>     Name of the function.

Functions already record their name (twice!), and it is simple enough to query 
func.__name__. What reason is there for recording it a third time, in the 
Signature object?

Besides, I don't consider the name of the function part of the function's 
signature. Functions can have multiple names, or no name at all, and the 
calling signature remains the same.

Even if we limit the discussion to distinct functions (rather than a single 
function with multiple names), I consider spam(x, y, z) ham(x, y, z) and 
eggs(x, y, z) to have the same signature. Otherwise, it makes it difficult to 
talk about one function having the same signature as another function, unless 
they also have the same name. Which would be unfortunate.


> * qualname : str
>     Fully qualified name of the function.

What's the fully qualified name of the function, and why is it needed?



[...]
> The structure of the Parameter object is:

> * is_args : bool
>     True if the parameter accepts variable number of arguments
>     (``\*args``-like), else False.


"args" is just a common name for the parameter, not for the kind of parameter. 
*args (or *data, *whatever) is a varargs parameter, and so the attribute 
should be called "is_varargs".


> * is_kwargs : bool
>     True if the parameter accepts variable number of keyword
>     arguments (``\*\*kwargs``-like), else False.

Likewise for **kwargs (or **kw, etc.) I'm not sure if there is a common 
convention for keyword varargs, so I see two options:

is_varkwargs
is_kwvarargs


> * 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.

What to do about parameters which are partly implemented? E.g. mode='spam' is 
implemented but mode='ham' is not.

Is there a use-case for is_implemented?

[...]
> Annotation Checker

>         def check_type(sig, arg_name, arg_type, arg_value):
>             # Internal function that incapsulates arguments type checking

/s/incapsulates/encapsulates



> Open Issues
> ===========

inspect.getfullargspec is currently unable to introspect builtin functions and 
methods. Should builtins gain a __signature__ so they can be introspected?



> 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").

+1



> Deprecate ``inspect.getfullargspec()`` and ``inspect.getcallargs()``?
> ---------------------------------------------------------------------


-1

> 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.

I think it is way to soon to deprecate anything. I don't think we should even 
consider PendingDeprecation until at least 3.4.

Actually, I would go further: leave getfullargspec to extract the *actual* 
argument spec from the code object, and __signature__ to be the claimed 
argument spec. Earlier, you state:

"Changes to the Signature object, or to any of its data members,
do not affect the function itself."

which leaves the possibility that __signature__ may no longer match the actual 
argument spec, for some reason. If you remove getfullargspec, people will have 
to reinvent it to deal with such cases.




-- 
Steven



More information about the Python-Dev mailing list