Update from Yury.

This commit is contained in:
Brett Cannon 2012-06-15 13:56:20 -04:00
parent e22d1112c4
commit 5f54b726b5
1 changed files with 126 additions and 38 deletions

View File

@ -51,12 +51,13 @@ A Signature object has the following public attributes and methods:
as listed in ``code.co_varnames``). as listed in ``code.co_varnames``).
* bind(\*args, \*\*kwargs) -> BoundArguments * bind(\*args, \*\*kwargs) -> BoundArguments
Creates a mapping from positional and keyword arguments to Creates a mapping from positional and keyword arguments to
parameters. Raises a ``BindError`` (subclass of ``TypeError``) parameters. Raises a ``TypeError`` if the passed arguments do
if the passed arguments do not match the signature. not match the signature.
* bind_partial(\*args, \*\*kwargs) -> BoundArguments * bind_partial(\*args, \*\*kwargs) -> BoundArguments
Works the same way as ``bind()``, but allows the omission Works the same way as ``bind()``, but allows the omission
of some required arguments (mimics ``functools.partial`` of some required arguments (mimics ``functools.partial``
behavior.) behavior.) Raises a ``TypeError`` if the passed arguments do
not match the signature.
* format(...) -> str * format(...) -> str
Formats the Signature object to a string. Optional arguments allow Formats the Signature object to a string. Optional arguments allow
for custom render functions for parameter names, for custom render functions for parameter names,
@ -84,27 +85,53 @@ The structure of the Parameter object is:
* name : str * name : str
The name of the parameter as a string. The name of the parameter as a string.
* default : object * default : object
The default value for the parameter, if specified. If the The default value for the parameter, if specified. If the
parameter has no default value, this attribute is not set. parameter has no default value, this attribute is not set.
* annotation : object * annotation : object
The annotation for the parameter if specified. If the The annotation for the parameter if specified. If the
parameter has no annotation, this attribute is not set. parameter has no annotation, this attribute is not set.
* is_keyword_only : bool
True if the parameter is keyword-only, else False. * kind : str
* is_args : bool Describes how argument values are bound to the parameter.
True if the parameter accepts variable number of arguments Possible values:
(``*args``-like), else False.
* is_kwargs : bool * ``Parameter.POSITIONAL_ONLY`` - value must be supplied
True if the parameter accepts variable number of keyword as a positional argument.
arguments (``**kwargs``-like), else False.
* is_implemented : bool Python has no explicit syntax for defining positional-only
parameters, but many builtin and extension module functions
(especially those that accept only one or two parameters)
accept them.
* ``Parameter.POSITIONAL_OR_KEYWORD`` - value may be
supplied as either a keyword or positional argument
(this is the standard binding behaviour for functions
implemented in Python.)
* ``Parameter.KEYWORD_ONLY`` - value must be supplied
as a keyword argument. Keyword only parameters are those
which appear after a "*" or "\*args" entry in a Python
function definition.
* ``Parameter.VAR_POSITIONAL`` - a tuple of positional
arguments that aren't bound to any other parameter.
This corresponds to a "\*args" parameter in a Python
function definition.
* ``Parameter.VAR_KEYWORD`` - a dict of keyword arguments
that aren't bound to any other parameter. This corresponds
to a "\*\*kwds" parameter in a Python function definition.
* implemented : bool
True if the parameter is implemented for use. Some platforms True if the parameter is implemented for use. Some platforms
implement functions but can't support specific parameters implement functions but can't support specific parameters
(e.g. "mode" for ``os.mkdir``). Passing in an unimplemented (e.g. "mode" for ``os.mkdir``). Passing in an unimplemented
parameter may result in the parameter being ignored, parameter may result in the parameter being ignored,
or in NotImplementedError being raised. It is intended that or in NotImplementedError being raised. It is intended that
all conditions where ``is_implemented`` may be False be all conditions where ``implemented`` may be False be
thoroughly documented. thoroughly documented.
Two parameters are equal when all their attributes are equal. Two parameters are equal when all their attributes are equal.
@ -159,12 +186,11 @@ The function implements the following algorithm:
- If it is ``None`` and the object is an instance of - If it is ``None`` and the object is an instance of
``BuiltinFunction``, raise a ``ValueError`` ``BuiltinFunction``, raise a ``ValueError``
- If the object is a an instance of ``FunctionType``: - If it has a ``__wrapped__`` attribute, return
``signature(object.__wrapped__)``
- If it has a ``__wrapped__`` attribute, return - If the object is a an instance of ``FunctionType`` construct
``signature(object.__wrapped__)`` and return a new ``Signature`` for it
- Or else construct a new ``Signature`` object and return it
- If the object is a method or a classmethod, construct and return - If the object is a method or a classmethod, construct and return
a new ``Signature`` object, with its first parameter (usually a new ``Signature`` object, with its first parameter (usually
@ -223,6 +249,9 @@ Examples
Visualizing Callable Objects' Signature Visualizing Callable Objects' Signature
--------------------------------------- ---------------------------------------
Let's define some classes and functions:
:: ::
from inspect import signature from inspect import signature
@ -245,25 +274,62 @@ Visualizing Callable Objects' Signature
return a, b, c return a, b, c
print('FooMeta >', str(signature(FooMeta))) def shared_vars(*shared_args):
print('Foo >', str(signature(Foo))) """Decorator factory that defines shared variables that are
print('Foo.__call__ >', str(signature(Foo.__call__))) passed to every invocation of the function"""
print('Foo().__call__ >', str(signature(Foo().__call__)))
print('partial(Foo().__call__, 1, c=3) >', def decorator(f):
str(signature(partial(Foo().__call__, 1, c=3)))) @wraps(f)
print('partial(partial(Foo().__call__, 1, c=3), 2, c=20) >', def wrapper(*args, **kwds):
str(signature(partial(partial(Foo().__call__, 1, c=3), 2, c=20)))) full_args = shared_args + args
return f(*full_args, **kwds)
# Override signature
sig = wrapper.__signature__ = signature(f)
for __ in shared_args:
sig.parameters.popitem(last=False)
return wrapper
return decorator
The script will output: @shared_vars({})
def example(_state, a, b, c):
return _state, a, b, c
def format_signature(obj):
return str(signature(obj))
Now, in the python REPL:
:: ::
FooMeta > (name, bases, dct, *, bar:bool=False) >>> format_signature(FooMeta)
Foo > (spam:int=42) '(name, bases, dct, *, bar:bool=False)'
Foo.__call__ > (self, a, b, *, c) -> tuple
Foo().__call__ > (a, b, *, c) -> tuple >>> format_signature(Foo)
partial(Foo().__call__, 1, c=3) > (b, *, c=3) -> tuple '(spam:int=42)'
partial(partial(Foo().__call__, 1, c=3), 2, c=20) > (*, c=20) -> tuple
>>> format_signature(Foo.__call__)
'(self, a, b, *, c) -> tuple'
>>> format_signature(Foo().__call__)
'(a, b, *, c) -> tuple'
>>> 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 Annotation Checker
@ -317,14 +383,14 @@ Annotation Checker
else: else:
if not isinstance(default, type_): if not isinstance(default, type_):
raise ValueError("{func}: wrong type of a default value for {arg!r}". \ raise ValueError("{func}: wrong type of a default value for {arg!r}". \
format(func=sig.qualname, arg=param.name)) format(func=func.__qualname__, arg=param.name))
def check_type(sig, arg_name, arg_type, arg_value): def check_type(sig, arg_name, arg_type, arg_value):
# Internal function that encapsulates arguments type checking # Internal function that encapsulates arguments type checking
if not isinstance(arg_value, arg_type): if not isinstance(arg_value, arg_type):
raise ValueError("{func}: wrong type of {arg!r} argument, " \ raise ValueError("{func}: wrong type of {arg!r} argument, " \
"{exp!r} expected, got {got!r}". \ "{exp!r} expected, got {got!r}". \
format(func=sig.qualname, arg=arg_name, format(func=func.__qualname__, arg=arg_name,
exp=arg_type.__name__, got=type(arg_value).__name__)) exp=arg_type.__name__, got=type(arg_value).__name__))
@functools.wraps(func) @functools.wraps(func)
@ -341,12 +407,12 @@ Annotation Checker
# OK, we have a type for the argument, lets get the corresponding # OK, we have a type for the argument, lets get the corresponding
# parameter description from the signature object # parameter description from the signature object
param = sig.parameters[arg_name] param = sig.parameters[arg_name]
if param.is_args: if param.kind == param.VAR_POSITIONAL:
# If this parameter is a variable-argument parameter, # If this parameter is a variable-argument parameter,
# then we need to check each of its values # then we need to check each of its values
for value in arg: for value in arg:
check_type(sig, arg_name, type_, value) check_type(sig, arg_name, type_, value)
elif param.is_kwargs: elif param.kind == param.VAR_KEYWORD:
# If this parameter is a variable-keyword-argument parameter: # If this parameter is a variable-keyword-argument parameter:
for subname, value in arg.items(): for subname, value in arg.items():
check_type(sig, arg_name + ':' + subname, type_, value) check_type(sig, arg_name + ':' + subname, type_, value)
@ -364,13 +430,35 @@ Annotation Checker
else: else:
if isinstance(return_type, type) and not isinstance(result, return_type): if isinstance(return_type, type) and not isinstance(result, return_type):
raise ValueError('{func}: wrong return type, {exp} expected, got {got}'. \ raise ValueError('{func}: wrong return type, {exp} expected, got {got}'. \
format(func=sig.qualname, exp=return_type.__name__, format(func=func.__qualname__, exp=return_type.__name__,
got=type(result).__name__)) got=type(result).__name__))
return result return result
return wrapper return wrapper
Render Function Signature to HTML
---------------------------------
::
import inspect
def format_to_html(func):
sig = inspect.signature(func)
html = sig.format(token_params_separator='<span class="t-comma">,</span>',
token_colon='<span class="t-colon">:</span>',
token_eq='<span class="t-eq">=</span>',
token_return_annotation='<span class="t-ra">-&gt;</span>',
token_left_paren='<span class="t-lp">(</span>',
token_right_paren='<span class="t-lp">)</span>',
token_kwonly_separator='<span class="t-ast">*</span>',
format_name=lambda name: '<span class="name">'+name+'</span>')
return '<span class="py-func">{}</span>'.format(html)
References References
========== ==========