Expand available documentation with more detail on decorator usage.
This commit is contained in:
473
docs/decorators.rst
Normal file
473
docs/decorators.rst
Normal file
@@ -0,0 +1,473 @@
|
|||||||
|
Decorators
|
||||||
|
==========
|
||||||
|
|
||||||
|
The **wrapt** module provides various components, but the main reason that
|
||||||
|
it likely is to be used is for creating decorators. This document covers the
|
||||||
|
creation of decorators and all the information needed to cover what you can
|
||||||
|
do within the wrapper function linked to your decorator.
|
||||||
|
|
||||||
|
Creating Decorators
|
||||||
|
-------------------
|
||||||
|
|
||||||
|
To implement your decorator you need to first define a wrapper function.
|
||||||
|
This will be called each time a decorated function is called. The wrapper
|
||||||
|
function needs to take four positional arguments:
|
||||||
|
|
||||||
|
* ``wrapped`` - The wrapped function which in turns needs to be called by your wrapper function.
|
||||||
|
* ``instance`` - The object to which the wrapped function was bound when it was called.
|
||||||
|
* ``args`` - The list of positional arguments supplied when the decorated function was called.
|
||||||
|
* ``kwargs`` - The dictionary of keyword arguments supplied when the decorated function was called.
|
||||||
|
|
||||||
|
The wrapper function would do whatever it needs to, but would usually in
|
||||||
|
turn call the wrapped function that is passed in via the ``wrapped``
|
||||||
|
argument.
|
||||||
|
|
||||||
|
The decorator ``@wrapt.decorator`` then needs to be applied to the wrapper
|
||||||
|
function to convert it into a decorator which can in turn be applied to
|
||||||
|
other functions.
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
|
import wrapt
|
||||||
|
|
||||||
|
@wrapt.decorator
|
||||||
|
def pass_through(wrapped, instance, args, kwargs):
|
||||||
|
return wrapped(*args, **kwargs)
|
||||||
|
|
||||||
|
@pass_through
|
||||||
|
def function():
|
||||||
|
pass
|
||||||
|
|
||||||
|
Decorators With Arguments
|
||||||
|
-------------------------
|
||||||
|
|
||||||
|
If you wish to implement a decorator which accepts arguments, then wrap the
|
||||||
|
definition of the decorator in a function closure. Any arguments supplied
|
||||||
|
to the outer function when the decorator is applied, will be available to
|
||||||
|
the inner wrapper when the wrapped function is called.
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
|
import wrapt
|
||||||
|
|
||||||
|
def with_arguments(myarg1, myarg2):
|
||||||
|
@wrapt.decorator
|
||||||
|
def wrapper(wrapped, instance, args, kwargs):
|
||||||
|
return wrapper(*args, **kwargs)
|
||||||
|
return wrapper
|
||||||
|
|
||||||
|
@with_arguments(1, 2)
|
||||||
|
def function():
|
||||||
|
pass
|
||||||
|
|
||||||
|
If using Python 3, you can use the keyword arguments only syntax to force
|
||||||
|
use of keyword arguments when the decorator is used.
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
|
import wrapt
|
||||||
|
|
||||||
|
def with_keyword_only_arguments(*, myarg1, myarg2):
|
||||||
|
@wrapt.decorator
|
||||||
|
def wrapper(wrapped, instance, args, kwargs):
|
||||||
|
return wrapped(*args, **kwargs)
|
||||||
|
return wrapper
|
||||||
|
|
||||||
|
@with_keyword_only_arguments(myarg1=1, myarg2=2)
|
||||||
|
def function():
|
||||||
|
pass
|
||||||
|
|
||||||
|
Processing Function Arguments
|
||||||
|
-----------------------------
|
||||||
|
|
||||||
|
The original set of positionl arguments and keyword arguments supplied when
|
||||||
|
the decorated function is called will be passed in the ``args`` and
|
||||||
|
``kwargs`` arguments.
|
||||||
|
|
||||||
|
Note that these are always passed as their own unique arguments and are not
|
||||||
|
broken out and bound in any way to the decorator wrapper arguments. In
|
||||||
|
other words, the decorater wrapper function signature must always be::
|
||||||
|
|
||||||
|
@wrapt.decorator
|
||||||
|
def my_decorator(wrapped, instance, args, kwargs): # CORRECT
|
||||||
|
return wrapped(*args, **kwargs)
|
||||||
|
|
||||||
|
You cannot use::
|
||||||
|
|
||||||
|
@wrapt.decorator
|
||||||
|
def my_decorator(wrapped, instance, *args, **kwargs): # WRONG
|
||||||
|
return wrapped(*args, **kwargs)
|
||||||
|
|
||||||
|
nor can you specify actual named arguments to which ``args`` and ``kwargs``
|
||||||
|
would be bound.
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
|
@wrapt.decorator
|
||||||
|
def my_decorator(wrapped, instance, arg1, arg2): # WRONG
|
||||||
|
return wrapped(arg1, arg2)
|
||||||
|
|
||||||
|
Separate arguments are used and no binding performed to avoid the
|
||||||
|
possibility of name collisions between the arguments passed to a decorated
|
||||||
|
function when called, and the names used for the ``wrapped`` and
|
||||||
|
``instance`` arguments. This can happen for example were ``wrapped`` and
|
||||||
|
``instance`` also used as keyword arguments by the wrapped function.
|
||||||
|
|
||||||
|
If needing to modify certain arguments being supplied to the decorated
|
||||||
|
function when called, you will thus need to trigger binding of the
|
||||||
|
arguments yourself. This can be done using a nested function which in turn
|
||||||
|
then calls the wrapped function::
|
||||||
|
|
||||||
|
@wrapt.decorator
|
||||||
|
def my_decorator(wrapped, instance, args, kwargs):
|
||||||
|
def _execute(arg1, arg2, *_args, **_kwargs):
|
||||||
|
|
||||||
|
# Do something with arg1 and arg2 and then pass the
|
||||||
|
# modified values to the wrapped function. Use 'args'
|
||||||
|
# and 'kwargs' on the nested function to mop up any
|
||||||
|
# unexpected or non required arguments so they can
|
||||||
|
# still be passed through to the wrapped function.
|
||||||
|
|
||||||
|
return wrapped(arg1, arg2, *_args, **_kwargs)
|
||||||
|
|
||||||
|
return _execute(*args, **kwargs)
|
||||||
|
|
||||||
|
If you do not need to modify the arguments being passed through to the
|
||||||
|
wrapped function, but still need to extract them so as to log them or
|
||||||
|
otherwise use them as input into some process you could instead use.
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
|
@wrapt.decorator
|
||||||
|
def my_decorator(wrapped, instance, args, kwargs):
|
||||||
|
def _arguments(arg1, arg2, *args, **kwargs):
|
||||||
|
return (arg1, arg2)
|
||||||
|
|
||||||
|
arg1, arg2 = _arguments(*args, **kwargs)
|
||||||
|
|
||||||
|
# Do something with arg1 and arg2 but still pass through
|
||||||
|
# the original arguments to the wrapped function.
|
||||||
|
|
||||||
|
return wrapped(*args, **kwargs)
|
||||||
|
|
||||||
|
You should not simply attempt to extract positional arguments from ``args``
|
||||||
|
directly because this will fail if those positional arguments were actually
|
||||||
|
passed as keyword arguments, and so were passed in ``kwargs`` with ``args``
|
||||||
|
being an empty tuple.
|
||||||
|
|
||||||
|
Function Argument Specifications
|
||||||
|
--------------------------------
|
||||||
|
|
||||||
|
To obtain the argument specification of a decorated function the standard
|
||||||
|
``getargspec()`` function from the ``inspect`` module can be used.
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
|
@wrapt.decorator
|
||||||
|
def my_decorator(wrapped, instance, args, kwargs):
|
||||||
|
return wrapped(*args, **kwargs)
|
||||||
|
|
||||||
|
@my_decorator
|
||||||
|
def function(arg1, arg2):
|
||||||
|
pass
|
||||||
|
|
||||||
|
>>> print(inspect.getargspec(function))
|
||||||
|
ArgSpec(args=['arg1', 'arg2'], varargs=None, keywords=None, defaults=None)
|
||||||
|
|
||||||
|
If using Python 3, the ``getfullargspec()`` or ``signature()`` functions
|
||||||
|
from the ``inspect`` module can also be used.
|
||||||
|
|
||||||
|
In other words, applying a decorator created using ``@wrapt.decorator`` to
|
||||||
|
a function is signature preserving and does not result in the loss of the
|
||||||
|
original argument specification as would occur when more simplistic
|
||||||
|
decorator patterns are used.
|
||||||
|
|
||||||
|
Wrapped Function Documentation
|
||||||
|
------------------------------
|
||||||
|
|
||||||
|
To obtain documentation for a decorated function which may be specified in
|
||||||
|
a documentation string of the original wrapped function, the standard
|
||||||
|
Python help system can be used.
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
|
@wrapt.decorator
|
||||||
|
def my_decorator(wrapped, instance, args, kwargs):
|
||||||
|
return wrapped(*args, **kwargs)
|
||||||
|
|
||||||
|
@my_decorator
|
||||||
|
def function(arg1, arg2):
|
||||||
|
"""Function documentation."""
|
||||||
|
pass
|
||||||
|
|
||||||
|
>>> help(function)
|
||||||
|
Help on function function in module __main__:
|
||||||
|
|
||||||
|
function(arg1, arg2)
|
||||||
|
Function documentation.
|
||||||
|
|
||||||
|
Just the documentation string itself can still be obtained by accessing the
|
||||||
|
``__doc__`` attribute of the decorated function.
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
|
>>> print(function.__doc__)
|
||||||
|
Function documentation.
|
||||||
|
|
||||||
|
Wrapped Function Source Code
|
||||||
|
----------------------------
|
||||||
|
|
||||||
|
To obtain the argument specification of a decorated function the standard
|
||||||
|
``getsource()`` function from the ``inspect`` module can be used.
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
|
@wrapt.decorator
|
||||||
|
def my_decorator(wrapped, instance, args, kwargs):
|
||||||
|
return wrapped(*args, **kwargs)
|
||||||
|
|
||||||
|
@my_decorator
|
||||||
|
def function(arg1, arg2):
|
||||||
|
pass
|
||||||
|
|
||||||
|
>>> print(inspect.getsource(function))
|
||||||
|
@my_decorator
|
||||||
|
def function(arg1, arg2):
|
||||||
|
pass
|
||||||
|
|
||||||
|
As with signatures, the use of the decorator does not prevent access to the
|
||||||
|
original source code for the wrapped function.
|
||||||
|
|
||||||
|
Signature Changing Decorators
|
||||||
|
-----------------------------
|
||||||
|
|
||||||
|
When using ``inspect.getargspec()`` the argument specification for the
|
||||||
|
original wrapped function is returned. If however the decorator is a
|
||||||
|
signature changing decorator, this is not going to be what is desired.
|
||||||
|
|
||||||
|
In this circumstance it is necessary to pass a dummy function to the
|
||||||
|
decorator via the optional ``adapter`` argument. When this is done, the
|
||||||
|
argument specification will be sourced from the prototype for this dummy
|
||||||
|
function.
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
|
def _my_adpater_prototype(arg1, arg2): pass
|
||||||
|
|
||||||
|
@wrapt.decorator(adapter=_my_adpater_prototype)
|
||||||
|
def my_adapter(wrapped, instance, args, kwargs):
|
||||||
|
"""Adapter documentation."""
|
||||||
|
|
||||||
|
def _execute(arg1, arg2, *_args, **_kwargs):
|
||||||
|
|
||||||
|
# We actually multiply the first two arguments together
|
||||||
|
# and pass that in as a single argument. The prototype
|
||||||
|
# exposed by the decorator is thus different to that of
|
||||||
|
# the wrapped function.
|
||||||
|
|
||||||
|
return wrapped(arg1*arg2, *_args, **_kwargs)
|
||||||
|
|
||||||
|
return _execute(*args, **kwargs)
|
||||||
|
|
||||||
|
@my_adapter
|
||||||
|
def function(arg):
|
||||||
|
"""Function documentation."""
|
||||||
|
|
||||||
|
pass
|
||||||
|
|
||||||
|
>>> help(function)
|
||||||
|
Help on function function in module __main__:
|
||||||
|
|
||||||
|
function(arg1, arg2)
|
||||||
|
Function documentation.
|
||||||
|
|
||||||
|
As it would not be accidental that you applied such a signature changing
|
||||||
|
decorator to a function, it would normally be the case that such usage
|
||||||
|
would be explained within the documentation for the wrapped function. As
|
||||||
|
such, the documentation for the wrapped function is still what is used for
|
||||||
|
the ``__doc__`` string and what would appear when using the Python help
|
||||||
|
system. In the latter, the arguments required of the adapter would though
|
||||||
|
instead appear.
|
||||||
|
|
||||||
|
Decorating Functions
|
||||||
|
--------------------
|
||||||
|
|
||||||
|
When applying a decorator to a normal function, the ``instance`` argument
|
||||||
|
would always be ``None``.
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
|
@wrapt.decorator
|
||||||
|
def pass_through(wrapped, instance, args, kwargs):
|
||||||
|
return wrapped(*args, **kwargs)
|
||||||
|
|
||||||
|
@pass_through
|
||||||
|
def function(arg1, arg2):
|
||||||
|
pass
|
||||||
|
|
||||||
|
function(1, 2)
|
||||||
|
|
||||||
|
Decorating Instance Methods
|
||||||
|
---------------------------
|
||||||
|
|
||||||
|
When applying a decorator to an instance method, the ``instance`` argument
|
||||||
|
will be the instance of the class on which the instance method is called.
|
||||||
|
That is, it would be the same as ``self`` passed as the first argument to
|
||||||
|
the actual instance method.
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
|
@wrapt.decorator
|
||||||
|
def pass_through(wrapped, instance, args, kwargs):
|
||||||
|
return wrapped(*args, **kwargs)
|
||||||
|
|
||||||
|
class Class(object):
|
||||||
|
|
||||||
|
@pass_through
|
||||||
|
def function_im(self, arg1, arg2):
|
||||||
|
pass
|
||||||
|
|
||||||
|
c = Class()
|
||||||
|
|
||||||
|
c.function_im(1, 2)
|
||||||
|
|
||||||
|
Class.function_im(c, 1, 2)
|
||||||
|
|
||||||
|
Note that the ``self`` argument is only passed via ``instance``, it is not
|
||||||
|
passed as part of ``args``. Only the arguments following on from the ``self``
|
||||||
|
argument will be a part of args.
|
||||||
|
|
||||||
|
When calling the wrapped function in the decorator wrapper function, the
|
||||||
|
``instance`` should never be passed explicitly though. This is because the
|
||||||
|
instance is already bound to ``wrapped`` and will be passed automatically
|
||||||
|
as the first argument to the original wrapped function.
|
||||||
|
|
||||||
|
This is even the situation where the instance method was called via the
|
||||||
|
class type and the ``self`` pointer passed explicitly. This is the case
|
||||||
|
as the decorator identifies this specific case and adjusts ``instance``
|
||||||
|
and ``args`` so that the decorator wrapper function does not see it as
|
||||||
|
being any different to where it was called directly on the instance.
|
||||||
|
|
||||||
|
Decorating Class Methods
|
||||||
|
------------------------
|
||||||
|
|
||||||
|
When applying a decorator to a class method, the ``instance`` argument will
|
||||||
|
be the class type on which the class method is called. That is, it would be
|
||||||
|
the same as ``cls`` passed as the first argument to the actual class
|
||||||
|
method.
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
|
@wrapt.decorator
|
||||||
|
def pass_through(wrapped, instance, args, kwargs):
|
||||||
|
return wrapped(*args, **kwargs)
|
||||||
|
|
||||||
|
class Class(object):
|
||||||
|
|
||||||
|
@pass_through
|
||||||
|
@classmethod
|
||||||
|
def function_cm(cls, arg1, arg2):
|
||||||
|
pass
|
||||||
|
|
||||||
|
Class.function_cm(1, 2)
|
||||||
|
|
||||||
|
Note that the ``cls`` argument is only passed via ``instance``, it is not
|
||||||
|
passed as part of ``args``. Only the arguments following on from the ``cls``
|
||||||
|
argument will be a part of args.
|
||||||
|
|
||||||
|
When calling the wrapped function in the decorator wrapper function, the
|
||||||
|
``instance`` should never be passed explicitly though. This is because the
|
||||||
|
instance is already bound to ``wrapped`` and will be passed automatically
|
||||||
|
as the first argument to the original wrapped function.
|
||||||
|
|
||||||
|
Note that due to a bug in Python ``classmethod.__get__()``, whereby it does
|
||||||
|
not apply the descriptor protocol to the function wrapped by ``classmethod``,
|
||||||
|
the above only applies where the decorator wraps the ``@classmethod``
|
||||||
|
decorator. If the decorator is placed inside of the ``@classmethod``
|
||||||
|
decorator, then ``instance`` will be ``None`` and the decorator wrapper
|
||||||
|
function will see the call as being the same as a normal function. As a
|
||||||
|
result, always place any decorator outside of the ``@classmethod``
|
||||||
|
decorator.
|
||||||
|
|
||||||
|
Decorating Static Methods
|
||||||
|
-------------------------
|
||||||
|
|
||||||
|
When applying a decorator to a static method, the ``instance`` argument
|
||||||
|
will be ``None``. In other words, the decorator wrapper function will not
|
||||||
|
be able to distinguish a call to a static method from a normal function.
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
|
@wrapt.decorator
|
||||||
|
def pass_through(wrapped, instance, args, kwargs):
|
||||||
|
return wrapped(*args, **kwargs)
|
||||||
|
|
||||||
|
class Class(object):
|
||||||
|
|
||||||
|
@pass_through
|
||||||
|
@staticmethod
|
||||||
|
def function_sm(arg1, arg2):
|
||||||
|
pass
|
||||||
|
|
||||||
|
Class.function_sm(1, 2)
|
||||||
|
|
||||||
|
Decorating Classes
|
||||||
|
------------------
|
||||||
|
|
||||||
|
When applying a decorator to a class, the ``instance`` argument will be
|
||||||
|
``None``. In order to distinguish this case from a normal function call,
|
||||||
|
``inspect.isclass()`` should be used on ``wrapped`` to determine if it
|
||||||
|
is a class type.
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
|
@wrapt.decorator
|
||||||
|
def pass_through(wrapped, instance, args, kwargs):
|
||||||
|
return wrapped(*args, **kwargs)
|
||||||
|
|
||||||
|
@pass_through
|
||||||
|
class Class(object):
|
||||||
|
pass
|
||||||
|
|
||||||
|
c = Class()
|
||||||
|
|
||||||
|
Universal Decorators
|
||||||
|
--------------------
|
||||||
|
|
||||||
|
A universal decorator is one that can be applied to different types of
|
||||||
|
functions and can adjust automatically based on what is being decorated.
|
||||||
|
|
||||||
|
For exapmple, the decorator may be able to be used on both a normal
|
||||||
|
function and an instance method, thereby avoiding the need to create two
|
||||||
|
separate decorators to be used in each case.
|
||||||
|
|
||||||
|
A universal decorator can be created by observing what has been stated
|
||||||
|
above in relation to the expected values/types for ``wrapped`` and
|
||||||
|
``instance`` passed to the decorator wrapper function.
|
||||||
|
|
||||||
|
These rules can be summarised by the following.
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
|
import inspect
|
||||||
|
|
||||||
|
@wrapt.decorator
|
||||||
|
def universal(wrapped, instance, args, kwargs):
|
||||||
|
if instance is None:
|
||||||
|
if inspect.isclass(wrapped):
|
||||||
|
# Decorator was applied to a class.
|
||||||
|
return wrapped(*args, **kwargs)
|
||||||
|
else:
|
||||||
|
# Decorator was applied to a function or staticmethod.
|
||||||
|
return wrapped(*args, **kwargs)
|
||||||
|
else:
|
||||||
|
if inspect.isclass(instance):
|
||||||
|
# Decorator was applied to a classmethod.
|
||||||
|
return wrapped(*args, **kwargs)
|
||||||
|
else:
|
||||||
|
# Decorator was applied to an instancemethod.
|
||||||
|
return wrapped(*args, **kwargs)
|
||||||
|
|
||||||
|
To be truly robust, a universal decorator should raise a runtime exception
|
||||||
|
at the point it is subsequently called, when it was applied as a decorator
|
||||||
|
in a scenario it does not support.
|
2
docs/examples.rst
Normal file
2
docs/examples.rst
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
Code Examples
|
||||||
|
=============
|
@@ -29,6 +29,11 @@ Contents
|
|||||||
:maxdepth: 1
|
:maxdepth: 1
|
||||||
|
|
||||||
quick-start
|
quick-start
|
||||||
|
decorators
|
||||||
benchmarks
|
benchmarks
|
||||||
known-issues
|
known-issues
|
||||||
unit-testing
|
unit-testing
|
||||||
|
|
||||||
|
.. proxies
|
||||||
|
.. wrappers
|
||||||
|
.. examples
|
||||||
|
2
docs/proxies.rst
Normal file
2
docs/proxies.rst
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
Object Proxies
|
||||||
|
==============
|
2
docs/wrappers.rst
Normal file
2
docs/wrappers.rst
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
Function Wrappers
|
||||||
|
=================
|
Reference in New Issue
Block a user