Python 3.3’s inspect module has introduced signature objects, which provide a new way to introspect callable objects and a protocol for pretending to have a different call signature.
This package helps module authors enhance their callables’ introspectability if needed, for instance for documentation, and provides a way to use features the python syntax does not or did not permit, such as keyword-only parameters.
For versions of Python below 3.3, a backport of the relevant parts of inspect is available on pypi.
The functions in this module can be used as decorators to mark and enforce some parameters to be keyword-only or annotate them, just like you can using Python 3 syntax. You can also mark and enforce parameters to be positional-only. autokwoargs helps you quickly make your parameters with default values become keyword-only.
Annotates a function, avoiding the use of python3 syntax
These two functions are equivalent:
def py3_func(spam: 'ham', eggs: 'chicken'=False) -> 'return':
return spam, eggs
@annotate('return', spam='ham', eggs='chicken')
def py23_func(spam, eggs=False):
return spam, eggs
Parameters: |
|
---|---|
Raises: | ValueError if a parameter to be annotated does not exist on the function |
Marks the given parameters as keyword-only, avoiding the use of python3 syntax.
These two functions are equivalent:
def py3_func(spam, *, ham, eggs='chicken'):
return spam, ham, eggs
@kwoargs('ham', 'eggs')
def py23_func(spam, ham, eggs='chichen'):
return spam, ham, eggs
Parameters: | |
---|---|
Raises: | ValueError if end or one of posoarg_names isn’t in the decorated function’s signature. |
Marks all arguments with default values as keyword-only.
Parameters: | exceptions (sequence) – names of parameters not to convert |
---|
>>> from sigtools.modifiers import autokwoargs
>>> @autokwoargs(exceptions=['c'])
... def func(a, b, c=3, d=4, e=5):
... pass
...
>>> from inspect import signature
>>> print(signature(func))
(a, b, c=3, *, d=4, e=5)
Marks the given parameters as positional-only.
If the resulting function is passed any named arguments that references a positional parameter, TypeError will be raised.
>>> from sigtools.modifiers import posoargs
>>> @posoargs('ham')
... def func(ham, spam):
... pass
...
>>> func('ham', 'spam')
>>> func('ham', spam='spam')
>>> func(ham='ham', spam='spam')
Traceback (most recent call last):
File "<input>", line 1, in <module>
File "./sigtools/modifiers.py", line 94, in __call__
.format(' '.join(repr(name) for name in intersect))
TypeError: Named arguments refer to positional-only parameters: 'ham'
Parameters: | |
---|---|
Raises: | ValueError if end or one of posoarg_names isn’t in the decorated function’s signature. |
The forwards_to_* decorators from this module will leave a “note” on the decorated object for sigtools.specifiers.signature to pick up. These “notes” tell signature in which way the signature of the examinated object should be crafted. The forwards_to_* decorators here will help you tell introspection or documentation tools what the *args and **kwargs parameters stand for in your function if it forwards them to another callable. This should cover most use cases, but you can use forger_function or set_signature_forger to create your own.
Retrieve the signature of obj, taking into account any specifier from this module.
You can use emulate=True as an argument to the specifiers from this module if you wish them to work with inspect.signature or its funcsigs backport directly.
>>> from sigtools import specifiers
>>> import inspect
>>> def inner(a, b):
... return a + b
...
>>> @specifiers.forwards_to_function(inner)
... def outer(c, *args, **kwargs):
... return c * inner(*args, **kwargs)
...
>>> print(inspect.signature(outer))
(c, *args, **kwargs)
>>> print(specifiers.signature(outer))
(c, a, b)
>>> @specifiers.forwards_to_function(inner, emulate=True)
... def outer(c, *args, **kwargs):
... return c * inner(*args, **kwargs)
#fixme
Wraps the decorated function to give it the effective signature it has when it forwards its *args and **kwargs to the static callable wrapped.
>>> from sigtools.specifiers import forwards_to_function
>>> def wrapped(x, y):
... return x * y
...
>>> @forwards_to_function(wrapped)
... def wrapper(a, *args, **kwargs):
... return a + wrapped(*args, **kwargs)
...
>>> from inspect import signature
>>> print(signature(wrapper))
(a, x, y)
See Picking the appropriate arguments for forwards for more information on the parameters.
Wraps the decorated method to give it the effective signature it has when it forwards its *args and **kwargs to the method named by wrapped_name.
Parameters: | wrapped_name (str) – The name of the wrapped method. |
---|
See Picking the appropriate arguments for forwards for more information on the parameters.
>>> from sigtools.specifiers import signature, forwards_to_method
>>> class Ham(object):
... def egg(self, a, b):
... return a + b
... @forwards_to_method('egg')
... def spam(self, c, *args, **kwargs):
... return c * self.egg(*args, **kwargs)
...
>>> h = Ham()
>>> print(signature(h.spam))
(c, a, b)
Wraps the decorated method to give it the effective signature it has when it forwards its *args and **kwargs to the same method on the super object for the class it belongs in.
You can only use this decorator directly in Python versions 3.3 and up, and the wrapped function must make use of the arg-less form of super:
>>> from sigtools.specifiers import forwards_to_super
>>> class Base:
... def func(self, x, y):
... return x * y
..
>>> class Subclass(Base):
... @forwards_to_super()
... def func(self, a, *args, **kwargs):
... return a + super().func(*args, **kwargs)
...
>>> from inspect import signature
>>> print(signature(Subclass.func))
(self, a, x, y)
>>> print(signature(Subclass().func))
(a, x, y)
If you need to use similar functionality in older python versions, use apply_forwards_to_super instead.
See Picking the appropriate arguments for forwards for more information on the parameters.
Applies the forwards_to_super decorator on member_names in the decorated class, in a way which works in Python 2.6 and up.
>>> from sigtools.specifiers import apply_forwards_to_super
>>> class Base:
... def func(self, x, y):
... return x * y
...
>>> @apply_forwards_to_super('func')
... class Subclass(Base):
... def func(self, a, *args, **kwargs):
... return a + super(Subclass, self).func(*args, **kwargs)
...
>>> from inspect import signature
>>> print(signature(Subclass.func))
(self, a, x, y)
>>> print(signature(Subclass().func))
(a, x, y)
See Picking the appropriate arguments for forwards for more information on the parameters.
Returns an effective signature of wrapper when it forwards its *args and **kwargs to wrapped.
Parameters: | |
---|---|
Returns: | a inspect.Signature object |
See Picking the appropriate arguments for forwards for more information on the parameters.
Creates a decorator factory which, when applied will set func as the forger function of the decorated object.
Parameters: | func (callable) – Must return a fake signature for the object passed as the named argument obj. Any arguments supplied during decoration are also passed. |
---|
The decorator produced by this function also accepts an emulate parameter. See set_signature_forger for information on it.
This function can be used as a decorator:
>>> from sigtools import specifiers, modifiers, support
>>> @specifiers.forger_function
... @modifiers.kwoargs('obj')
... def static_signature(obj, sig):
... return sig
...
>>> @static_signature(support.s('a, b, /'))
... def my_func(d, e):
... pass
...
>>> print(specifiers.signature(my_func))
(a, b, /)
Attempts to set the given signature forger on the supplied object.
This function first tries to set an attribute on obj and returns it. If that fails, it wraps the object that advertises the correct signature (even to inspect.signature) and forwards calls.
Parameters: | emulate – If supplied, forces the function to adhere to one strategy: either set the attribute or fail(False), or always wrap the object(True). If something else is passed, it is called with (obj, forger) and the return value is used. |
---|
The functions here help you combine multiple functions into a new callable which will automatically advertise the correct signature.
Bases: builtins.object
Creates a callable that passes the first argument through each callable, using the result of each pass as the argument to the next
Turns a function into a decorator that wraps callables with that function.
Consult signatures.forwards‘s documentation for help picking the correct values for the parameters.
The wrapped function is passed as first argument to the wrapper.
As an example, here we create a @print_call decorator which wraps the decorated function and prints a line everytime the function is called:
>>> from sigtools import modifiers, wrappers
>>> @wrappers.wrapper_decorator
... @modifiers.autokwoargs
... def print_call(func, _show_return=True, *args, **kwargs):
... print('Calling {0.__name__}(*{1}, **{2})'.format(func, args, kwargs))
... ret = func(*args, **kwargs)
... if _show_return:
... print('Return: {0!r}'.format(ret))
... return ret
...
>>> print_call
<decorate with <<function print_call at 0x7f28d721a950> with signature print_cal
l(func, *args, _show_return=True, **kwargs)>>
>>> @print_call
... def as_list(obj):
... return [obj]
...
>>> as_list
<<function as_list at 0x7f28d721ad40> decorated with <<function print_call at 0x
7f28d721a950> with signature print_call(func, *args, _show_return=True, **kwargs
)>>
>>> from inspect import signature
>>> print(signature(as_list))
(obj, *, _show_return=True)
>>> as_list('ham')
Calling as_list(*('ham',), **{})
Return: ['ham']
['ham']
>>> as_list('spam', _show_return=False)
Calling as_list(*('spam',), **{})
['spam']
For introspection purposes, returns an iterable that yields each wrapping function of obj(as done through wrapper_decorator, outermost wrapper first.
Continuing from the wrapper_decorator example:
>>> list(wrappers.wrappers(as_list))
[<<function print_call at 0x7f28d721a950> with signature print_call(func, *args,
_show_return=True, **kwargs)>]
The functions here are high-level operations that produce a signature from other signature objects, as opposed to dealing with each parameter individually. They are most notably used by the decorators from sigtools.specifiers to compute combined signatures.
Tries to compute a signature for which a valid call would also validate the given signatures.
It guarantees any call that conforms to the merged signature will conform to all the given signatures. However, some calls that don’t conform to the merged signature may actually work on all the given ones regardless.
Parameters: | signatures (inspect.Signature) – The signatures to merge together. |
---|---|
Returns: | a inspect.Signature object |
Raises: | IncompatibleSignatures |
>>> from sigtools import signatures, support
>>> print(signatures.merge(
... support.s('one, two, *args, **kwargs'),
... support.s('one, two, three, *, alpha, **kwargs'),
... support.s('one, *args, beta, **kwargs')
... ))
(one, two, three, *, alpha, beta, **kwargs)
The resulting signature does not necessarily validate all ways of conforming to the underlying signatures:
>>> from sigtools import signatures
>>> from inspect import signature
>>>
>>> def left(alpha, *args, **kwargs):
... return alpha
...
>>> def right(beta, *args, **kwargs):
... return beta
...
>>> sig_left = signature(left)
>>> sig_right = signature(right)
>>> sig_merged = signatures.merge(sig_left, sig_right)
>>>
>>> print(sig_merged)
(alpha, /, *args, **kwargs)
>>>
>>> kwargs = {'alpha': 'a', 'beta': 'b'}
>>> left(**kwargs), right(**kwargs) # both functions accept the call
('a', 'b')
>>>
>>> sig_merged.bind(**kwargs) # the merged signature doesn't
Traceback (most recent call last):
File "<input>", line 1, in <module>
File "/usr/lib64/python3.4/inspect.py", line 2642, in bind
return args[0]._bind(args[1:], kwargs)
File "/usr/lib64/python3.4/inspect.py", line 2542, in _bind
raise TypeError(msg) from None
TypeError: 'alpha' parameter is positional only, but was passed as a keyword
Embeds a signature within another’s *args and **kwargs parameters, as if a function with the outer signature called a function with the inner signature with just f(*args, **kwargs).
Parameters: |
|
---|---|
Returns: | a inspect.Signature object |
Raises: |
>>> from sigtools import signatures, support
>>> print(signatures.embed(
... support.s('one, *args, **kwargs'),
... support.s('two, *args, kw, **kwargs'),
... support.s('last'),
... ))
(one, two, last, *, kw)
>>> # use signatures.mask() to remove self-like parameters
>>> print(signatures.embed(
... support.s('self, *args, **kwargs'),
... signatures.mask(
... support.s('self, *args, keyword, **kwargs'), 1),
... ))
(self, *args, keyword, **kwargs)
Removes the given amount of positional parameters and the given named parameters from sig.
Parameters: |
|
---|---|
Returns: | a inspect.Signature object |
Raises: | ValueError if the signature cannot handle the arguments to be passed. |
>>> from sigtools import signatures, support
>>> print(signatures.mask(support.s('a, b, *, c, d'), 1, 'd'))
(b, *, c)
>>> print(signatures.mask(support.s('a, b, *args, c, d'), 3, 'd'))
(*args, c)
>>> print(signatures.mask(support.s('*args, c, d'), 2, 'd', hide_varargs=True))
(*, c)
Calls mask on inner, then returns the result of calling embed with outer and the result of mask.
Parameters: |
|
---|
use_varargs and use_varkwargs are the same parameters as in embed, and num_args, named_args are parameters of mask.
Returns: | the resulting inspect.Signature object |
---|---|
Raises: | IncompatibleSignatures |
>>> from sigtools import support, signatures
>>> print(signatures.forwards(
... support.s('a, *args, x, **kwargs'),
... support.s('b, c, *, y, z'),
... 1, 'y'))
(a, c, *, x, z)
Bases: builtins.ValueError
Raised when two or more signatures are incompatible for the requested operation.
Variables: |
|
---|
Classifies the parameters from sig.
Parameters: | sig (inspect.Signature) – The signature to operate on |
---|---|
Returns: | A tuple (posargs, pokargs, varargs, kwoargs, varkwas) |
Return type: | (list, list, Parameter or None, dict, Parameter or None) |
>>> from sigtools import signatures, support
>>> from pprint import pprint
>>> pprint(signatures.sort_params(support.s('a, /, b, *args, c, d')))
([<Parameter at 0x7fdda4e89418 'a'>],
[<Parameter at 0x7fdda4e89470 'b'>],
<Parameter at 0x7fdda4e89c58 'args'>,
{'c': <Parameter at 0x7fdda4e89c00 'c'>,
'd': <Parameter at 0x7fdda4e89db8 'd'>},
None)
Reverses sort_params‘s operation.
Returns: | A new inspect.Signature object based off sig, with the given parameters. |
---|
Creates a signature from the given string representation of one.
Warning
The contents of the arguments are eventually passed to exec. Do not use with untrusted input.
>>> from sigtools.support import s
>>> sig = s('a, b=2, *args, c:"annotation", **kwargs')
>>> sig
<inspect.Signature object at 0x7f15e6055550>
>>> print(sig)
(a, b=2, *args, c:'annotation', **kwargs)
Creates a dummy function that has the signature represented by sig_str and returns a tuple containing the arguments passed, in order.
Warning
The contents of the arguments are eventually passed to exec. Do not use with untrusted input.
>>> from sigtools.support import f
>>> import inspect
>>> func = f('a, b=2, *args, c:"annotation", **kwargs')
>>> print(inspect.signature(func))
(a, b=2, *args, c:'annotation', **kwargs)
>>> func(1, c=3)
{'b': 2, 'a': 1, 'kwargs': {}, 'args': ()}
>>> func(1, 2, 3, 4, c=5, d=6)
{'b': 2, 'a': 1, 'kwargs': {'d': 6}, 'args': (3, 4)}
Reads a string representation of a signature and returns a tuple func_code can understand.
Formats the code to construct a function to read_sig‘s design.
Executes the given code and returns the object named func from the resulting namespace.
Creates a dummy function from the given signature object
Warning
The contents of the arguments are eventually passed to exec. Do not use with untrusted input.
Figures out reasonably as many ways as possible to call a callable with the given signature.
Returns a dict with each parameter name from sig mapped to values from args, kwargs as if a function with sig was called with (*args, **kwargs).
Similar to inspect.Signature.bind.
Determines which ways to call sig in callsigs are valid or not.
Returns: | Two lists: (valid, invalid).
|
---|
Tests if a function is coherent with its signature.
Parameters: |
|
---|---|
Raises: | AssertionError |
At the moment sphinx.ext.autodoc can only automatically discover the signatures of basic callables. This extension makes it use sigtools.specifiers.signature on the callable instead.
Enable it by appending 'sigtools.sphinxext' to the extensions list in your Sphinx conf.py
When filling the parameters for the various declinations of forwards, you are telling it how your wrapper function is calling the wrapped function and how the wrapper function’s *args and **kwargs fit in this. If the wrapped function’s parameters can’t be satisfied or if you declare passing parameters it cannot handle, signatures.IncompatibleSignatures will be raised.
Here’s an overview of the parameters for the family of forwards functions:
- num_args
- The number of arguments you pass by position, excluding *args.
- *named_args
- The names of the arguments you pass by name, excluding **kwargs.
- use_varargs=
- Tells if the wrapper’s *args is being passed to the wrapped function.
- use_varkwargs=
- Tells if the wrapper’s **kwargs is being passed to the wrapped function.
We will be using the ~specifiers.forwards_to_function decorator for these examples.
*args and **kwargs are forwarded directly if present
You do not need to signal anything about the wrapper function’s parameters:
@specifiers.forwards_to_function(wrapped)
def outer(arg, *args, **kwargs):
inner(*args, **kwargs)
This holds true even if you omit one of *args and **kwargs:
@specifiers.forwards_to_function(wrapped)
def outer(**kwargs):
inner(**kwargs)
Passing positional arguments to the wrapped function
Indicate the number of arguments you are passing to the wrapped function:
@specifiers.forwards_to_function(wrapped, 1)
def outer(*args, **kwargs):
inner('abc', *args, **kwargs)
This applies even if the argument comes from the wrapper:
@specifiers.forwards_to_function(wrapped, 1)
def outer(arg, *args, **kwargs):
inner(arg, *args, **kwargs)
Passing named arguments to from the wrapper
Pass the names of the arguments after num_args:
@specifiers.forwards_to_function(wrapped, 0, 'arg')
def outer(*args, **kwargs):
inner(*args, arg='abc', **kwargs)
Once again, the same goes for if that argument comes from the outer function’s:
@specifiers.forwards_to_function(wrapped, 0, 'arg')
def outer(*args, arg, **kwargs): # py 3
inner(*args, arg=arg, **kwargs)
If you combine positional and named arguments, follow the previous advice as well:
@specifiers.forwards_to_function(wrapped, 2, 'alpha', 'beta')
def outer(two, *args, beta, **kwargs):
inner(one, two=two, *args, alpha='abc', beta=beta, **kwargs)
When the outer function uses *args or **kwargs but doesn’t forward them to the inner function
Pass use_varargs=False if you outer function has an *args-like parameter but doesn’t use it on the inner function directly:
@specifiers.forwards_to_function(wrapped, use_varargs=False)
def outer(*args, **kwargs):
inner(**kwargs)
Pass use_varkwargs=False if you outer function has a **kwargs-like parameter but doesn’t use it on the inner function directly:
@specifiers.forwards_to_function(wrapped, use_varkwargs=False)
def outer(*args, **kwargs):
inner(*args)