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``).
* bind(\*args, \*\*kwargs) -> BoundArguments
Creates a mapping from positional and keyword arguments to
parameters. Raises a ``BindError`` (subclass of ``TypeError``)
if the passed arguments do not match the signature.
parameters. Raises a ``TypeError`` if the passed arguments do
not match the signature.
* bind_partial(\*args, \*\*kwargs) -> BoundArguments
Works the same way as ``bind()``, but allows the omission
of some required arguments (mimics ``functools.partial``
behavior.)
behavior.) Raises a ``TypeError`` if the passed arguments do
not match the signature.
* format(...) -> str
Formats the Signature object to a string. Optional arguments allow
for custom render functions for parameter names,
@ -84,27 +85,53 @@ The structure of the Parameter object is:
* name : str
The name of the parameter as a string.
* default : object
The default value for the parameter, if specified. If the
parameter has no default value, this attribute is not set.
* annotation : object
The annotation for the parameter if specified. If the
parameter has no annotation, this attribute is not set.
* is_keyword_only : bool
True if the parameter is keyword-only, else False.
* is_args : bool
True if the parameter accepts variable number of arguments
(``*args``-like), else False.
* is_kwargs : bool
True if the parameter accepts variable number of keyword
arguments (``**kwargs``-like), else False.
* is_implemented : bool
* kind : str
Describes how argument values are bound to the parameter.
Possible values:
* ``Parameter.POSITIONAL_ONLY`` - value must be supplied
as a positional argument.
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
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
all conditions where ``implemented`` may be False be
thoroughly documented.
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
``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
``signature(object.__wrapped__)``
- Or else construct a new ``Signature`` object and return it
- If the object is a an instance of ``FunctionType`` construct
and return a new ``Signature`` for it
- If the object is a method or a classmethod, construct and return
a new ``Signature`` object, with its first parameter (usually
@ -223,6 +249,9 @@ Examples
Visualizing Callable Objects' Signature
---------------------------------------
Let's define some classes and functions:
::
from inspect import signature
@ -245,25 +274,62 @@ Visualizing Callable Objects' Signature
return a, b, c
print('FooMeta >', str(signature(FooMeta)))
print('Foo >', str(signature(Foo)))
print('Foo.__call__ >', str(signature(Foo.__call__)))
print('Foo().__call__ >', str(signature(Foo().__call__)))
print('partial(Foo().__call__, 1, c=3) >',
str(signature(partial(Foo().__call__, 1, c=3))))
print('partial(partial(Foo().__call__, 1, c=3), 2, c=20) >',
str(signature(partial(partial(Foo().__call__, 1, c=3), 2, c=20))))
def shared_vars(*shared_args):
"""Decorator factory that defines shared variables that are
passed to every invocation of the function"""
def decorator(f):
@wraps(f)
def wrapper(*args, **kwds):
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)
Foo > (spam:int=42)
Foo.__call__ > (self, a, b, *, c) -> tuple
Foo().__call__ > (a, b, *, c) -> tuple
partial(Foo().__call__, 1, c=3) > (b, *, c=3) -> tuple
partial(partial(Foo().__call__, 1, c=3), 2, c=20) > (*, c=20) -> tuple
>>> 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(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
@ -317,14 +383,14 @@ Annotation Checker
else:
if not isinstance(default, type_):
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):
# Internal function that encapsulates arguments type checking
if not isinstance(arg_value, arg_type):
raise ValueError("{func}: wrong type of {arg!r} argument, " \
"{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__))
@functools.wraps(func)
@ -341,12 +407,12 @@ Annotation Checker
# OK, we have a type for the argument, lets get the corresponding
# parameter description from the signature object
param = sig.parameters[arg_name]
if param.is_args:
if param.kind == param.VAR_POSITIONAL:
# If this parameter is a variable-argument parameter,
# then we need to check each of its values
for value in arg:
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:
for subname, value in arg.items():
check_type(sig, arg_name + ':' + subname, type_, value)
@ -364,13 +430,35 @@ Annotation Checker
else:
if isinstance(return_type, type) and not isinstance(result, return_type):
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__))
return result
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
==========