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.
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:
Signature.empty.TypeError if the passed arguments donot match the signature.bind(), but allows the omissionof some required arguments (mimicsfunctools.partialbehavior.) Raises aTypeError if the passed arguments donot match the signature.replace 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:
Parameter 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.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())'()'
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:
POSITIONAL_ONLYparameters, which can have it set toNone.)Parameter.empty.Parameter.empty.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.
replaced was invoked on. To override a Parameterattribute, pass the corresponding argument. To removean attribute from aParameter, passParameter.empty.Parameter constructor:
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'"
Result of aSignature.bind call. Holds the mapping of argumentsto the function’s parameters.
Has the following public attributes:
bind() relied on a default value are skipped.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}
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:
__signature__ attribute and if itis notNone - return it__wrapped__ attribute, returnsignature(object.__wrapped__)FunctionType, constructand return a newSignature for itSignatureobject, 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.)functools.partial, constructa newSignature from itspartial.func attribute, andaccount for already boundpartial.args andpartial.kwargs__call__ method defined inits MRO, return a Signature for it__new__ method defined in its MRO,return a Signature object for it__init__ method defined in its MRO,return a Signature object for itsignature(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].
The first PEP design had a provision for implicit caching ofSignatureobjects in theinspect.signature() function. However, this has thefollowing downsides:
Signature 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__signature__ attribute for the caseswhen there is a need to explicitly set to aSignature object thatis different from the actual oneSome 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.
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.
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)'
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
PEP 362 was accepted by Guido, Friday, June 22, 2012[3] .The reference implementation was committed to trunk later that day.
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