Movatterモバイル変換


[0]ホーム

URL:


Following system colour schemeSelected dark colour schemeSelected light colour scheme

Python Enhancement Proposals

PEP 362 – Function Signature Object

PEP 362 – Function Signature Object

Author:
Brett Cannon <brett at python.org>, Jiwon Seo <seojiwon at gmail.com>,Yury Selivanov <yury at edgedb.com>, Larry Hastings <larry at hastings.org>
Status:
Final
Type:
Standards Track
Created:
21-Aug-2006
Python-Version:
3.3
Post-History:
04-Jun-2012
Resolution:
Python-Dev message

Table of Contents

Abstract

Python has always supported powerful introspection capabilities,including introspecting functions and methods (for the rest ofthis PEP, “function” refers to both functions and methods). Byexamining a function object you can fully reconstruct the function’ssignature. Unfortunately this information is stored in an inconvenientmanner, 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 functionand its parameters, and makes introspection easy and straightforward.

However, this object does not replace the existing functionmetadata, which is used by Python itself to execute thosefunctions. The new metadata object is intended solely to makefunction introspection easier for Python programmers.

Signature Object

A Signature object represents the call signature of a function andits return annotation. For each parameter accepted by the functionit stores aParameter object in itsparameters collection.

A Signature object has the following public attributes and methods:

  • return_annotation : object
    The “return” annotation for the function. If the functionhas no “return” annotation, this attribute is set toSignature.empty.
  • parameters : OrderedDict
    An ordered mapping of parameters’ names to the correspondingParameter objects.
  • bind(*args, **kwargs) -> BoundArguments
    Creates a mapping from positional and keyword arguments toparameters. Raises aTypeError if the passed arguments donot match the signature.
  • bind_partial(*args, **kwargs) -> BoundArguments
    Works the same way asbind(), but allows the omissionof some required arguments (mimicsfunctools.partialbehavior.) Raises aTypeError if the passed arguments donot match the signature.
  • replace(parameters=<optional>, *, return_annotation=<optional>) -> Signature
    Creates a new Signature instance based on the instancereplace was invoked on. It is possible to pass differentparameters and/orreturn_annotation to override thecorresponding properties of the base signature. To removereturn_annotation from the copiedSignature, pass inSignature.empty.

    Note that the ‘=<optional>’ notation, means that the argument isoptional. This notation applies to the rest of this PEP.

Signature objects are immutable. UseSignature.replace() tomake a modified copy:

>>>deffoo()->None:...pass>>>sig=signature(foo)>>>new_sig=sig.replace(return_annotation="new return annotation")>>>new_sigisnotsigTrue>>>new_sig.return_annotation!=sig.return_annotationTrue>>>new_sig.parameters==sig.parametersTrue>>>new_sig=new_sig.replace(return_annotation=new_sig.empty)>>>new_sig.return_annotationisSignature.emptyTrue

There are two ways to instantiate a Signature class:

  • Signature(parameters=<optional>, *, return_annotation=Signature.empty)
    Default Signature constructor. Accepts an optional sequenceofParameter objects, and an optionalreturn_annotation.Parameters sequence is validated to check that there are noparameters with duplicate names, and that the parametersare in the right order, i.e. positional-only first, thenpositional-or-keyword, etc.
  • Signature.from_function(function)
    Returns a Signature object reflecting the signature of thefunction passed in.

It’s possible to test Signatures for equality. Two signatures areequal when their parameters are equal, their positional andpositional-only parameters appear in the same order, and theyhave equal return annotations.

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

Signature also implements__str__:

>>>str(Signature.from_function((lambda*args:None)))'(*args)'>>>str(Signature())'()'

Parameter Object

Python’s expressive syntax means functions can accept many differentkinds of parameters with many subtle semantic differences. Wepropose a rich Parameter object designed to represent any possiblefunction parameter.

A Parameter object has the following public attributes and methods:

  • name : str
    The name of the parameter as a string. Must be a validpython identifier name (with the exception ofPOSITIONAL_ONLYparameters, which can have it set toNone.)
  • default : object
    The default value for the parameter. If the parameter has nodefault value, this attribute is set toParameter.empty.
  • annotation : object
    The annotation for the parameter. If the parameter has noannotation, this attribute is set toParameter.empty.
  • kind
    Describes how argument values are bound to the parameter.Possible values:
    • Parameter.POSITIONAL_ONLY - value must be suppliedas a positional argument.

      Python has no explicit syntax for defining positional-onlyparameters, but many built-in and extension module functions(especially those that accept only one or two parameters)accept them.

    • Parameter.POSITIONAL_OR_KEYWORD - value may besupplied as either a keyword or positional argument(this is the standard binding behaviour for functionsimplemented in Python.)
    • Parameter.KEYWORD_ONLY - value must be suppliedas a keyword argument. Keyword only parameters are thosewhich appear after a “*” or “*args” entry in a Pythonfunction definition.
    • Parameter.VAR_POSITIONAL - a tuple of positionalarguments that aren’t bound to any other parameter.This corresponds to a “*args” parameter in a Pythonfunction definition.
    • Parameter.VAR_KEYWORD - a dict of keyword argumentsthat aren’t bound to any other parameter. This correspondsto a “**kwargs” parameter in a Python function definition.

    Always useParameter.* constants for setting and checkingvalue of thekind attribute.

  • replace(*, name=<optional>, kind=<optional>, default=<optional>, annotation=<optional>) -> Parameter
    Creates a new Parameter instance based on the instancereplaced was invoked on. To override a Parameterattribute, pass the corresponding argument. To removean attribute from aParameter, passParameter.empty.

Parameter constructor:

  • Parameter(name, kind, *, annotation=Parameter.empty, default=Parameter.empty)
    Instantiates a Parameter object.name andkind are required,whileannotation anddefault are optional.

Two parameters are equal when they have equal names, kinds, defaults,and annotations.

Parameter objects are immutable. Instead of modifying a Parameter object,you can useParameter.replace() to create a modified copy like so:

>>>param=Parameter('foo',Parameter.KEYWORD_ONLY,default=42)>>>str(param)'foo=42'>>>str(param.replace())'foo=42'>>>str(param.replace(default=Parameter.empty,annotation='spam'))"foo:'spam'"

BoundArguments Object

Result of aSignature.bind call. Holds the mapping of argumentsto the function’s parameters.

Has the following public attributes:

  • arguments : OrderedDict
    An ordered, mutable mapping of parameters’ names to arguments’ values.Contains only explicitly bound arguments. Arguments forwhichbind() relied on a default value are skipped.
  • args : tuple
    Tuple of positional arguments values. Dynamically computed fromthe ‘arguments’ attribute.
  • kwargs : dict
    Dict of keyword arguments values. Dynamically computed fromthe ‘arguments’ attribute.

Thearguments attribute should be used in conjunction withSignature.parameters for any arguments processing purposes.

args andkwargs properties can be used to invoke functions:

deftest(a,*,b):...sig=signature(test)ba=sig.bind(10,b=20)test(*ba.args,**ba.kwargs)

Arguments which could be passed as part of either*args or**kwargswill be included only in theBoundArguments.args attribute. Consider thefollowing example:

deftest(a=1,b=2,c=3):passsig=signature(test)ba=sig.bind(a=10,c=13)>>>ba.args(10,)>>>ba.kwargs:{'c':13}

Implementation

The implementation adds a new functionsignature() to theinspectmodule. The function is the preferred way of getting aSignature fora 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 itis notNone - return it
  • If it has a__wrapped__ attribute, returnsignature(object.__wrapped__)
  • If the object is an instance ofFunctionType, constructand return a newSignature for it
  • If the object is a bound method, construct and return a newSignatureobject, with its first parameter (usuallyself orcls)removed. (classmethod andstaticmethod are supportedtoo. Since both are descriptors, the former returns a bound method,and the latter returns its wrapped function.)
  • If the object is an instance offunctools.partial, constructa newSignature from itspartial.func attribute, andaccount for already boundpartial.args andpartial.kwargs
  • If the object is a class or metaclass:
    • If the object’s type has a__call__ method defined inits MRO, return a Signature for it
    • If the object has a__new__ method defined in its MRO,return a Signature object for it
    • If the object has a__init__ method defined in its MRO,return a Signature object for it
  • Returnsignature(object.__call__)

Note that theSignature object is created in a lazy manner, andis not automatically cached. However, the user can manually cache aSignature by storing it in the__signature__ attribute.

An implementation for Python 3.3 can be found at[1].The python issue tracking the patch is[2].

Design Considerations

No implicit caching of Signature objects

The first PEP design had a provision for implicit caching ofSignatureobjects in theinspect.signature() function. However, this has thefollowing downsides:

  • If theSignature object is cached then any changes to the functionit describes will not be reflected in it. However, If the caching isneeded, it can be always done manually and explicitly
  • It is better to reserve the__signature__ attribute for the caseswhen there is a need to explicitly set to aSignature object thatis different from the actual one

Some functions may not be introspectable

Some functions may not be introspectable in certain implementations ofPython. For example, in CPython, built-in functions defined in C provideno metadata about their arguments. Adding support for them is out ofscope for this PEP.

Signature and Parameter equivalence

We assume that parameter names have semantic significance–twosignatures are equal only when their corresponding parameters are equaland have the exact same names. Users who want looser equivalence tests,perhaps ignoring names of VAR_KEYWORD or VAR_POSITIONAL parameters, willneed to implement those themselves.

Examples

Visualizing Callable Objects’ Signature

Let’s define some classes and functions:

frominspectimportsignaturefromfunctoolsimportpartial,wrapsclassFooMeta(type):def__new__(mcls,name,bases,dct,*,bar:bool=False):returnsuper().__new__(mcls,name,bases,dct)def__init__(cls,name,bases,dct,**kwargs):returnsuper().__init__(name,bases,dct)classFoo(metaclass=FooMeta):def__init__(self,spam:int=42):self.spam=spamdef__call__(self,a,b,*,c)->tuple:returna,b,c@classmethoddefspam(cls,a):returnadefshared_vars(*shared_args):"""Decorator factory that defines shared variables that are       passed to every invocation of the function"""defdecorator(f):@wraps(f)defwrapper(*args,**kwargs):full_args=shared_args+argsreturnf(*full_args,**kwargs)# Override signaturesig=signature(f)sig=sig.replace(tuple(sig.parameters.values())[1:])wrapper.__signature__=sigreturnwrapperreturndecorator@shared_vars({})defexample(_state,a,b,c):return_state,a,b,cdefformat_signature(obj):returnstr(signature(obj))

Now, in the python REPL:

>>>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(Foo.spam)'(a)'>>>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

importinspectimportfunctoolsdefchecktypes(func):'''Decorator to verify arguments and return types    Example:        >>> @checktypes        ... def test(a:int, b:str) -> int:        ...     return int(a * b)        >>> test(10, '1')        1111111111        >>> test(10, 1)        Traceback (most recent call last):          ...        ValueError: foo: wrong type of 'b' argument, 'str' expected, got 'int'    '''sig=inspect.signature(func)types={}forparaminsig.parameters.values():# Iterate through function's parameters and build the list of# arguments typestype_=param.annotationiftype_isparam.emptyornotinspect.isclass(type_):# Missing annotation or not a type, skip itcontinuetypes[param.name]=type_# If the argument has a type specified, let's check that its# default value (if present) conforms with the type.ifparam.defaultisnotparam.emptyandnotisinstance(param.default,type_):raiseValueError("{func}: wrong type of a default value for{arg!r}". \format(func=func.__qualname__,arg=param.name))defcheck_type(sig,arg_name,arg_type,arg_value):# Internal function that encapsulates arguments type checkingifnotisinstance(arg_value,arg_type):raiseValueError("{func}: wrong type of{arg!r} argument, " \"{exp!r} expected, got{got!r}". \format(func=func.__qualname__,arg=arg_name,exp=arg_type.__name__,got=type(arg_value).__name__))@functools.wraps(func)defwrapper(*args,**kwargs):# Let's bind the argumentsba=sig.bind(*args,**kwargs)forarg_name,arginba.arguments.items():# And iterate through the bound argumentstry:type_=types[arg_name]exceptKeyError:continueelse:# OK, we have a type for the argument, lets get the corresponding# parameter description from the signature objectparam=sig.parameters[arg_name]ifparam.kind==param.VAR_POSITIONAL:# If this parameter is a variable-argument parameter,# then we need to check each of its valuesforvalueinarg:check_type(sig,arg_name,type_,value)elifparam.kind==param.VAR_KEYWORD:# If this parameter is a variable-keyword-argument parameter:forsubname,valueinarg.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 correctreturn_type=sig.return_annotationif(return_typeisnotsig._emptyandisinstance(return_type,type)andnotisinstance(result,return_type)):raiseValueError('{func}: wrong return type,{exp} expected, got{got}'. \format(func=func.__qualname__,exp=return_type.__name__,got=type(result).__name__))returnresultreturnwrapper

Acceptance

PEP 362 was accepted by Guido, Friday, June 22, 2012[3] .The reference implementation was committed to trunk later that day.

References

[1]
pep362 branch (https://bitbucket.org/1st1/cpython/overview)
[2]
issue 15008 (http://bugs.python.org/issue15008)
[3]
“A Desperate Plea For Introspection (aka: BDFAP Needed)” (https://mail.python.org/pipermail/python-dev/2012-June/120682.html)

Copyright

This document has been placed in the public domain.


Source:https://github.com/python/peps/blob/main/peps/pep-0362.rst

Last modified:2025-02-01 08:59:27 GMT


[8]ページ先頭

©2009-2026 Movatter.jp