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
|
||||
|
||||
quick-start
|
||||
decorators
|
||||
benchmarks
|
||||
known-issues
|
||||
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