Let's create a Python "decorator". Create a generic decorator framework.
To make. First, present the decorator framework (goal) and work towards it.
This article is written to be read down (toward the goal by repeating trial and error), and since it is a long sentence, I will describe only how to write the decorator (framework, framework) first. Include the test [Full version listed last](# decorator_frameworkpy-full-version). If you are busy, please take a look at it when you have time.
decorator_framework.py
from functools import wraps
def my_decorator( *args, **kwargs ):
#For decorators that clearly take no arguments, start here
# _my_You can rename the decorator and define it globally
def _my_decorator( func ):
# _my_decorator_body()If there is any necessary processing before defining, write it here
print( "_my_decorator_body()If there is any necessary processing before defining, write it here" )
@wraps(func)
def _my_decorator_body( *body_args, **body_kwargs ):
#Pre-processing is done here
print( "Pre-processing is done here", args, kwargs, body_args, body_kwargs )
try:
#Execution of the decorated body
ret = func( *body_args, **body_kwargs )
except:
raise
#Post-processing is done here
print( "Post-processing is done here", args, kwargs, body_args, body_kwargs )
return ret
#Write here if processing is required when the decorator is listed#2
print( "Write here if processing is required when the decorator is listed#2" )
return _my_decorator_body
#This is the end of the decorator that clearly takes no arguments
#Write here if processing is required when the decorator is listed#1
print( "Write here if processing is required when the decorator is listed#1" )
if len(args) == 1 and callable( args[0] ):
#If the decorator is called with no arguments, handle it here
print( "No arguments" )
return _my_decorator( args[0] )
else:
#If the decorator is called with arguments, handle it here
print( "There are some arguments:", args )
return _my_decorator
Even simpler.
decorator_framework.py Simple version
from functools import wraps
def my_decorator( *args, **kwargs ):
def _my_decorator( func ):
# _my_decorator_body()If there is any necessary processing before defining, write it here
@wraps(func)
def _my_decorator_body( *body_args, **body_kwargs ):
#Pre-processing is done here
try:
#Execution of the decorated body
ret = func( *body_args, **body_kwargs )
except:
raise
#Post-processing is done here
return ret
#Write here if processing is required when the decorator is listed#2
return _my_decorator_body
#Write here if processing is required when the decorator is listed#1
if len(args) == 1 and callable( args[0] ):
#If the decorator is called with no arguments, handle it here
return _my_decorator( args[0] )
else:
#If the decorator is called with arguments, handle it here
return _my_decorator
Now let's get down to creating a decorator.
The glossary describes the decorator as follows:
decorator dt> (decorator) A function that returns another function, usually
@wrapper span> code> syntax. A common use case for decorators is
< span class = "pre"> classmethod () span> code>
andstaticmethod () span> code>
. p>The decorator grammar is syntactic sugar. The following two function definitions are semantically identical: p>
def f(...): ... f = staticmethod(f)@staticmethod def f(...): ...
The same concept exists in classes, but it's rarely used. For more information on decorators, see function definition span> and class definition span> . p>
(Quoted from decorator-Glossary — Python 3.8.1 Documentation)
In the example quoted above, @ staticmethod
is the decorator.
In addition, the description of the function definition item is as follows.
Function definition is one or more decorator span> expression. When you define a function, the decorator expression is evaluated in the scope that contains the function definition. The result must be a callable object that takes a function object as its only argument. Instead of a function object, the returned value is bound to the function name. Multiple decorators are nested and applied. For example, the following code: p>
@f1(arg) @f2 def func(): passis roughly equivalent to p>
def func(): pass func = f1(arg)(f2(func))However, in the former code, you can temporarily bind the original function to the name
func span> code>. Except where there is no. p>
(Quoted from function definition --8 compound statement — Python 3.8.1 documentation)
Decorator grammar is syntactic sugar
Some people may not be familiar with the word "syntactic sugar". Simply put, syntactic sugar is what allows you to replace complex (obfuscated) descriptions with simple notation and vice versa.
In other words, decorators are rewritable replacements, which is similar to what other languages call "macro". (However, the replacement method has been decided)
Consider the example in the glossary citation.
Example 1
def f(...):
...
f = staticmethod(f)
With this example 1
Example 2
@staticmethod
def f(...):
...
It says that this example 2 is the same. What do you mean.
What Example 1 is doing is
f ()
.staticmethod ()
with the defined function f ()
(function object) as an argument.staticmethod ()
to the function f again.
It means that.Example 2 is a simplified version of this, where @staticmethod
is called a" decorator ".
After that, when the function f ()
is actually called, the redefined function is called as the return value of staticmethod ()
.
Since @staticmethod
is actually a standard-defined decorator, it's not a good sample, so we'll move on with @my_decorator
below.
There are a few things you can tell from the above example alone.
my_decorator
is a function. (To be exact, a "callable object")my_decorator ()
takes a function object as an argument.my_decorator ()
returns a function.about it.
The code below shows what we found above.
my_decorator First step
def my_decorator(func):
return func
It does nothing and returns the function received as an argument.
Calls that don't use @
look like sample 001.
sample 001
>>> def my_decorator_001(func):
... return func
...
>>> def base_func():
... print( "in base_func()" )
...
>>> base_func = my_decorator_001( base_func )
>>> base_func()
in base_func()
>>>
If you use @
as a decorator, it will look like sample 002.
sample 002
>>> def my_decorator_001(func):
... return func
...
>>> @my_decorator_001
... def base_func():
... print( "in base_func()" )
...
>>> base_func()
in base_func()
>>>
You don't know what's going on with this. However, it's worth noting that when you enter @ my_decorator_001
, it's waiting for continuous input.
Let's add a display inside my_decorator ()
.
sample 003
>>> def my_decorator_002(func):
... print( "in my_decorator_002()" )
... return func
...
>>> def base_func():
... print( "in base_func()" )
...
>>> base_func = my_decorator_002(base_func)
in my_decorator_002()
>>> base_func()
in base_func()
>>>
Next, let's use a decorator.
sample 004
>>> def my_decorator_002(func):
... print( "in my_decorator_002()" )
... return func
...
>>> @my_decorator_002
... def base_func():
... print( "in base_func()" )
...
in my_decorator_002()
>>> base_func()
in base_func()
>>>
The function my_decorator_002 ()
is not executed when you write @ my_decorator_002
, and ʻin my_decorator ()is displayed when you have finished defining
def base_func ():under it. The point to note is that this is the same thing that happens when you run
base_func = my_decorator_002 (base_func)` in sample 003.
Previously, the function given to the decorator argument was returned as-is with return. What is the return function?
Let's first try using a global function to see what happens if we return another function.
sample 005
>>> def global_func():
... print( "in global_func()" )
...
>>> def my_decorator_005(func):
... print( "in my_decorator_005()" )
... return global_func
...
>>> @my_decorator_005
... def base_func():
... print( "in base_func()" )
...
in my_decorator_005()
>>> base_func()
in global_func()
>>>
By executing base_func ()
, global_func ()
was called without any problem.
But, of course, the original base_func ()
is not running.
Consider executing the original function while executing the new function.
The original function is passed as an argument to the decorator function, so calling it will execute the original function.
sample 006
>>> def global_func():
... print( "in global_func()" )
...
>>> def my_decorator_006(func):
... print( "in my_decorator_006()" )
... func() #Call to the original function
... return global_func
...
>>> @my_decorator_006
... def base_func():
... print( "in base_func()" )
...
in my_decorator_006()
in base_func() #I have called the original function here
>>> base_func() #* I want to call the original function here
in global_func()
>>>
The original function base_func ()
was called when @ my_decorator_006
was specified.
The timing when you want to call base_func ()
is when you call base_func ()
with "*".
In other words, we want to call base_func ()
inside global_func ()
.
Let's modify it a bit so that it can be called inside global_func ()
.
sample 007
>>> #Keep the original function
... original_func = None
>>>
>>> def global_func():
... global original_func
... print( "in global_func()" )
... original_func() #The original function should be called
...
>>> def my_decorator_007(func):
... global original_func
... print( "in my_decorator_007()" )
... original_func = func #The func passed as an argument is the global original_To func
... return global_func
...
>>> @my_decorator_007
... def base_func():
... print( "in base_func()" )
...
in my_decorator_007()
>>> base_func() #* I want to call the original function here
in global_func()
in base_func()
>>>
It's a little confusing, but I was able to call it from global_func ()
by preparing the global variable ʻoriginal_funcand saving the
func` passed as an argument here.
However, there is one problem with this. When I try to use the decorator on multiple functions, it doesn't work as expected.
sample 008
>>> #Keep the original function
... original_func = None
>>>
>>> def global_func():
... global original_func
... print( "in global_func()" )
... original_func() #The original function should be called
...
>>> def my_decorator_007(func):
... global original_func
... print( "in my_decorator_007()" )
... original_func = func #The func passed as an argument is the global original_To func
... return global_func
...
>>> @my_decorator_007
... def base_func():
... print( "in base_func()" )
...
in my_decorator_007()
>>> @my_decorator_007
... def base_func_2():
... print( "in base_func_2()" )
...
in my_decorator_007()
>>> base_func() # "in base_func()"I want you to be displayed
in global_func()
in base_func_2() ← "in base_func()"not"in base_func_2()"Was displayed
>>> base_func_2() # "in base_func_2()"I want you to be displayed
in global_func()
in base_func_2()
>>>
The call to base_func ()
has executed base_func_2 ()
.
Since there is only one global variable global_func
, the last assigned base_func_2
is being executed.
Let's talk about "closures" so that we can use the decorator multiple times.
Take a look at sample_009.py
like this:
sample_009.py
1 def outer(outer_arg):
2 def inner(inner_arg):
3 # outer_arg and inner_Output arg
4 print( "outer_arg: " + outer_arg + ", inner_arg: " + inner_arg )
5 return inner # inner()Returns a function object
6
7 f1 = outer("first")
8 f2 = outer("second")
9
10 f1("f1")
11 f2("f2")
Execute f2 = outer ("second")
on line 8 afterf1 = outer ("first")
on line 7.
Each is assigned a ʻinnerfunction object, but what about
f1 ("f1") on line 10 and
f2 ("f2") `on line 11?
The 8th line says f2 = outer ("second ")
, so in the 4th line print ()
, ʻouter_arg becomes
"second" `.
The output of f1 ("f1 ")
on line 10 and f2 ("f2 ")
on line 11 are respectively.
outer_arg: second, inner_arg: f1
outer_arg: second, inner_arg: f2
It seems to be ...
When I run it, it looks like this:
sample 009
>>> def outer(outer_arg):
... def inner(inner_arg):
... # outer_arg and inner_Output arg
... print( "outer_arg:", outer_arg, ", inner_arg:", inner_arg )
... return inner # inner()Returns a function object
...
>>> f1 = outer("first")
>>> f2 = outer("second")
>>>
>>> f1("f1")
outer_arg: first, inner_arg: f1
>>> f2("f2")
outer_arg: second, inner_arg: f2
>>>
this is,
function definition
def inner (): ... is "executed" each time the ʻouter ()
function is called. referenced when the ʻinner ()
function is defined (executing the definition) is fixed (evaluated) and held by the inner.
This is to make the movement. This is called a closure.
Variables in the outer scope are evaluated at definition time, not at run time.Use this to allow the decorator to be used multiple times.
In sample 009, we used a global function, but in order to use the closure function, we define the function inside the decorator function.
sample_010.py
def my_decorator_010(func):
print( "in my_decorator_010()" )
def inner():
print( "in inner() and calling", func )
func()
print( "in inner() and returnd from ", func )
print( "in my_decorator_010(), leaving ..." )
return inner
@my_decorator_010
def base_func():
print( "in base_func()" )
@my_decorator_010
def base_func_2():
print( "in base_func_2()" )
base_func() # "in base_func()"I want you to be displayed
base_func_2() # "in base_func_2()"I want you to be displayed
Let's do this (enter interactively).
sample 010
>>> def my_decorator_010(func):
... print( "in my_decorator_010()" )
... def inner():
... print( "in inner() and calling", func )
... func()
... print( "in inner() and returnd from ", func )
... print( "in my_decorator_010(), leaving ..." )
... return inner
...
>>> @my_decorator_010
... def base_func():
... print( "in base_func()" )
...
in my_decorator_010()
in my_decorator_010(), leaving ...
>>> @my_decorator_010
... def base_func_2():
... print( "in base_func_2()" )
...
in my_decorator_010()
in my_decorator_010(), leaving ...
>>> base_func() # "in base_func()"I want you to be displayed
in inner() and calling <function base_func at 0x769d1858>
in base_func()
in inner() and returnd from <function base_func at 0x769d1858>
>>> base_func_2() # "in base_func_2()"I want you to be displayed
in inner() and calling <function base_func_2 at 0x769d1930>
in base_func_2()
in inner() and returnd from <function base_func_2 at 0x769d1930>
>>>
The results were as expected.
So far, we know that the decorator should be coded as follows:
sample 011
def my_decorator_011(func):
def inner():
#Write the pre-call process here
func()
#Write the post-call process here
return inner
The decorators up to this point are
If you want to make a general-purpose decorator, you need to meet the above two requirements.
The program considering the return value is as follows.
sample_012.py
def my_decorator_012(func):
def inner():
#Write the pre-call process here
ret = func()
#Write the post-call process here
return ret
return inner
@my_decorator_012
def base_func_1():
print( "in base_func_1()" )
return 1
@my_decorator_012
def base_func_2():
print( "in base_func_2()" )
r1 = base_func_1()
print( r1 )
base_func_2()
The execution result is as follows.
sample 012
>>> def my_decorator_012(func):
... def inner():
... #Write the pre-call process here
... ret = func()
... #Write the post-call process here
... return ret
... return inner
...
>>> @my_decorator_012
... def base_func_1():
... print( "in base_func_1()" )
... return 1
...
>>> @my_decorator_012
... def base_func_2():
... print( "in base_func_2()" )
...
>>> r1 = base_func_1()
in base_func_1()
>>> print( r1 )
1
>>> base_func_2()
in base_func_2()
>>>
It worked even if there was no return value (base_func_2 ()
).
See also: parameter-Glossary — Python 3.8.1 Documentation
Function arguments can be received as formal arguments for which the number of arguments and keyword specification are not determined by the variable-length position parameter and variable-length keyword parameter.
Variable length positions em>: Any number of position arguments (in addition to any position arguments already received by other formal arguments) Is specified. Such formal arguments should be preceded by the formal argument name, such as args em> below,
* span> < It can be defined by adding / code>: p>
def func(*args, **kwargs): ...Variable-length keywords em>: Given any number of keyword arguments (in addition to any keyword arguments already received by other formal arguments) Is specified. Such formal arguments should be
** strong before the formal parameter name, such as kwargs em> in the example above It can be defined by adding span> code>. p> li>
(Quoted from parameter-Glossary — Python 3.8.1 Documentation)
Simply put, you can take variadic arguments by specifying (* args, ** kwargs)
as function arguments.
Using this, the decorator for a function that takes an argument can be written as:
sample_013.py
def my_decorator_013(func):
def inner( *args, **kwargs ):
#Write the pre-call process here
ret = func( *args, **kwargs )
#Write the post-call process here
return ret
return inner
@my_decorator_013
def base_func_1(arg1, arg2, arg3="arg3"):
print( "in base_func_1({}, {}, {})".format(arg1, arg2, arg3 ) )
return 1
@my_decorator_013
def base_func_2():
print( "in base_func_2()" )
r1 = base_func_1("arg1","arg2")
print( r1 )
base_func_2()
The following is the execution result.
sample 013
>>> def my_decorator_013(func):
... def inner( *args, **kwargs ):
... #Write the pre-call process here
... ret = func( *args, **kwargs )
... #Write the post-call process here
... return ret
... return inner
...
>>> @my_decorator_013
... def base_func_1(arg1, arg2, arg3="arg3"):
... print( "in base_func_1({}, {}, {})".format(arg1, arg2, arg3 ) )
... return 1
...
>>> @my_decorator_013
... def base_func_2():
... print( "in base_func_2()" )
...
>>> r1 = base_func_1("arg1","arg2")
in base_func_1(arg1, arg2, arg3)
>>> print( r1 )
1
>>> base_func_2()
in base_func_2()
>>>
Also consider exceptions.
sample_014.py
def my_decorator_014(func):
def inner( *args, **kwargs ):
#Write the pre-call process here
try:
ret = func( *args, **kwargs )
except:
raise
#Write the post-call process here
return ret
return inner
@my_decorator_014
def base_func_1(arg1, arg2, arg3="arg3"):
print( "in base_func_1({}, {}, {})".format(arg1, arg2, arg3 ) )
return 1
@my_decorator_014
def base_func_2():
print( "in base_func_2()" )
na = 1 / 0 #Division by zero exception occurs
r1 = base_func_1("arg1","arg2")
print( r1 )
try:
base_func_2()
except ZeroDivisionError:
print( "Zero Division Error" )
The following is the execution result.
sample 014
>>> def my_decorator_014(func):
... def inner( *args, **kwargs ):
... #Write the pre-call process here
... try:
... ret = func( *args, **kwargs )
... except:
... raise
... #Write the post-call process here
... return ret
... return inner
...
>>> @my_decorator_014
... def base_func_1(arg1, arg2, arg3="arg3"):
... print( "in base_func_1({}, {}, {})".format(arg1, arg2, arg3 ) )
... return 1
...
>>> @my_decorator_014
... def base_func_2():
... print( "in base_func_2()" )
... na = 1 / 0 #Division by zero exception occurs
...
>>> r1 = base_func_1("arg1","arg2")
in base_func_1(arg1, arg2, arg3)
>>> print( r1 )
1
>>>
>>> try:
... base_func_2()
... except ZeroDivisionError:
... print( "Zero Division Error" )
...
in base_func_2()
Zero Division Error
>>>
Since a decorator is a function (a callable object), consider passing an argument to it.
The description of the function definition quoted at the beginning also included an example of a decorator with arguments.
@f1(arg)
@f2
def func(): pass
def func(): pass
func = f1(arg)(f2(func))
Since the decorators are nested, consider only one decorator for simplicity.
@my_decorator('argument')
def func(arg1, arg2, arg3):
pass
This is equivalent to below.
def func(arg1, arg2, arg3):
pass
func = my_decorator('argument')(func)
It means to execute _my_decorator (func)
using the function (let's say _my_decorator) returned by calling my_decorator ('argument')
.
The nesting becomes one step deeper, and the outermost function (decorator) receives the argument, and the decorator so far is included in it.
sample015.py
def my_decorator_015( arg1 ):
def _my_decorator( func ):
def inner( *args, **kwargs ):
print( "in inner, arg1={}, func={}".format(arg1, func.__name__) )
ret = func( *args, **kwargs )
print( "in inner leaving ..." )
return ret
return inner
return _my_decorator
The following is the execution result.
sample 015
>>> def my_decorator_015( arg1 ):
... def _my_decorator( func ):
... def inner( *args, **kwargs ):
... print( "in inner, arg1={}, func={}".format(arg1, func.__name__) )
... ret = func( *args, **kwargs )
... print( "in inner leaving ..." )
... return ret
... return inner
... return _my_decorator
...
>>> @my_decorator_015('argument')
... def f_015( arg1, arg2, arg3 ):
... print( "in func( {}, {}, {} )".format(arg1, arg2, arg3) )
...
>>> f_015( "Argument 1", "Argument 2", "Argument 3" )
in inner, arg1=argument, func=f_015
in func(Argument 1,Argument 2,Argument 3)
in inner leaving ...
>>>
When you call the decorator with arguments, the bottom part of arg1 and func are stored in _my_decorator_body.
print( "in _my_decorator_body, arg1={}, func={}".format(arg1, func.__name__) )
Therefore, when you call f_015 (), the saved arg1 and func are referenced.
The decorator that takes no arguments looks like this:
sample_014.py
def my_decorator_014(func):
def inner( *args, **kwargs ):
#Write the pre-call process here
try:
ret = func( *args, **kwargs )
except:
raise
#Write the post-call process here
return ret
return inner
The decorator that passes the arguments looks like this:
sample_016.py
def my_decorator_016( arg1 ):
def _my_decorator( func ):
def inner( *args, **kwargs ):
#Write the pre-call process here
try:
ret = func( *args, **kwargs )
except:
raise
#Write the post-call process here
return ret
return inner
return _my_decorator
What if you use a decorator that assumes arguments without giving any arguments?
sample_017.py
def my_decorator_017( arg1 ):
def _my_decorator( func ):
def inner( *args, **kwargs ):
#Write the pre-call process here
try:
ret = func( *args, **kwargs )
except:
raise
#Write the post-call process here
return ret
return inner
return _my_decorator
@my_decorator_017
def f_017( arg1, arg2, arg3 ):
print( "in {}( {}, {}, {} )".format(f_017.__name__, arg1, arg2, arg3) )
f_017( "Argument 1", "Argument 2", "Argument 3" )
Execution result
$ python sample_017.py
Traceback (most recent call last):
File "sample_017.py", line 21, in <module>
f_017( "Argument 1", "Argument 2", "Argument 3" )
TypeError: _my_decorator() takes 1 positional argument but 3 were given
$
This is because you haven't passed any arguments to @ my_decorator_017
, so it's called asf_017 = my_decorator_017 (f_017)
.
There are two ways to avoid this.
()
and receive it as a variadic argument.This is the first example.
sample_018.py
def my_decorator_018( *args, **kwargs ):
def _my_decorator( func ):
def inner( *inner_args, **inner_kwargs ):
#Write the pre-call process here
try:
ret = func( *inner_args, **inner_kwargs )
except:
raise
#Write the post-call process here
return ret
return inner
return _my_decorator
@my_decorator_018()
def f_018( arg1, arg2, arg3 ):
print( "in {}( {}, {}, {} )".format(f_018.__name__, arg1, arg2, arg3) )
f_018( "Argument 1", "Argument 2", "Argument 3" )
Execution result
$ python sample_018.py
in inner(Argument 1,Argument 2,Argument 3)
The second method uses the first argument of the decorator for the decision.
sample_019.py
def my_decorator_019( *args, **kwargs ):
def _my_decorator( func ):
def inner( *inner_args, **inner_kwargs ):
#Write the pre-call process here
try:
ret = func( *inner_args, **inner_kwargs )
except:
raise
#Write the post-call process here
return ret
return inner
if len(args) == 1 and callable(args[0]):
return _my_decorator( args[0] )
else:
return _my_decorator
@my_decorator_019
def f_019_1( arg1, arg2, arg3 ):
print( "in {}( {}, {}, {} )".format(f_019_1.__name__, arg1, arg2, arg3) )
@my_decorator_019()
def f_019_2( arg1, arg2, arg3 ):
print( "in {}( {}, {}, {} )".format(f_019_2.__name__, arg1, arg2, arg3) )
@my_decorator_019('arg')
def f_019_3( arg1, arg2, arg3 ):
print( "in {}( {}, {}, {} )".format(f_019_3.__name__, arg1, arg2, arg3) )
f_019_1( "Argument 1", "Argument 2", "Argument 3" )
f_019_2( "Argument 1", "Argument 2", "Argument 3" )
f_019_3( "Argument 1", "Argument 2", "Argument 3" )
To distinguish between the arguments passed to the decorator and the arguments passed when f_019_ * ()
is called, they are distinguished by (* args, ** kwargs)
and (* inner_args, ** inner_kwargs)
, respectively. doing.
Execution result
$ python sample_019.py
in inner(Argument 1,Argument 2,Argument 3)
in inner(Argument 1,Argument 2,Argument 3)
in inner(Argument 1,Argument 2,Argument 3)
Now that you have a general-purpose decorator framework ... I'd like to think ... the function names you called (f_019_ * .__ name__
) are all ʻinner`.
You can see that the function ʻinner is called instead of the original function. However, the original function may refer to attributes such as name (
name) and documentation (
doc`).
There is a decorator called @wraps
that can work around this.
See also: [@ functools.wraps () --functools --- Manipulating higher-order functions and callable objects — Python 3.8.1 documentation](https://docs.python.org/ja/3/library/functools.html? highlight = wraps # functools.wraps) See also: [functools.update_wrapper () --functools --- Manipulating higher-order functions and callable objects — Python 3.8.1 documentation](https://docs.python.org/ja/3/library/functools.html?highlight = wraps # functools.update_wrapper)
Decorating this before the def inner
allows you to wrap and preserve the attributes of the argument func
passed to the outer function (_my_decorator ()
).
sample_020.py
from functools import wraps
def my_decorator_020( *args, **kwargs ):
def _my_decorator( func ):
@wraps(func)
def inner( *inner_args, **inner_kwargs ):
#Write the pre-call process here
try:
ret = func( *inner_args, **inner_kwargs )
except:
raise
#Write the post-call process here
return ret
return inner
if len(args) == 1 and callable(args[0]):
return _my_decorator( args[0] )
else:
return _my_decorator
@my_decorator_020
def f_020_1( arg1, arg2, arg3 ):
print( "in {}( {}, {}, {} )".format(f_020_1.__name__, arg1, arg2, arg3) )
@my_decorator_020()
def f_020_2( arg1, arg2, arg3 ):
print( "in {}( {}, {}, {} )".format(f_020_2.__name__, arg1, arg2, arg3) )
@my_decorator_020('arg')
def f_020_3( arg1, arg2, arg3 ):
print( "in {}( {}, {}, {} )".format(f_020_3.__name__, arg1, arg2, arg3) )
f_020_1( "Argument 1", "Argument 2", "Argument 3" )
f_020_2( "Argument 1", "Argument 2", "Argument 3" )
f_020_3( "Argument 1", "Argument 2", "Argument 3" )
For sample_019.py
,@wraps (func)
is added before from functools import wraps
and def inner ()
.
func
is the function passed as an argument to_my_decorator ()
.
Execution result
$ python sample_020.py
in f_020_1(Argument 1,Argument 2,Argument 3)
in f_020_2(Argument 1,Argument 2,Argument 3)
in f_020_3(Argument 1,Argument 2,Argument 3)
$
The name of the function (f_019_ * .__ name__
) is retained as the name of the original function instead of ʻinner`.
It's been a long way, but finally the decorator framework is complete.
Since it was created generically, it is def my_decorator (* args, ** kwargs):
, but if the arguments to receive are fixed, it is better to clarify.
decorator_framework.py Full Version
decorator_framework.py Full Version
#!/usr/bin/env python
# -*- coding: utf-8 -*-
###########################################
#Decorator(decorator)
###########################################
from functools import wraps
def my_decorator( *args, **kwargs ):
"""
for doctest
>>> @my_decorator
... def f1( arg1 ):
... print( arg1 )
...
Write here if processing is required when the decorator is listed#1
No arguments
_my_decorator_body()If there is any necessary processing before defining, write it here
Write here if processing is required when the decorator is listed#2
>>> @my_decorator('mytest1')
... def f2( arg2 ):
... print( arg2 )
...
Write here if processing is required when the decorator is listed#1
There are some arguments: ('mytest1',)
_my_decorator_body()If there is any necessary processing before defining, write it here
Write here if processing is required when the decorator is listed#2
>>> @my_decorator
... def f3( arg1 ):
... print( arg1 )
... a = 1/0
...
Write here if processing is required when the decorator is listed#1
No arguments
_my_decorator_body()If there is any necessary processing before defining, write it here
Write here if processing is required when the decorator is listed#2
>>> @my_decorator('mytest2')
... def f4( arg2 ):
... print( arg2 )
... a = 1/0
...
Write here if processing is required when the decorator is listed#1
There are some arguments: ('mytest2',)
_my_decorator_body()If there is any necessary processing before defining, write it here
Write here if processing is required when the decorator is listed#2
>>> try:
... f1( "Hello, World! #1" )
... except:
... print( "error #1" )
...
Pre-processing is done here(<function f1 at 0x...>,) {} ('Hello, World! #1',) {}
Hello, World! #1
Post-processing is done here... {} ('Hello, World! #1',) {}
>>> try:
... f2( "Hello, World! #2" )
... except:
... print( "error #2" )
...
Pre-processing is done here('mytest1',) {} ('Hello, World! #2',) {}
Hello, World! #2
Post-processing is done here('mytest1',) {} ('Hello, World! #2',) {}
>>> try:
... f3( "Hello, World! #3" )
... except:
... print( "error #3" )
...
Pre-processing is done here(<function f3 at 0x...>,) {} ('Hello, World! #3',) {}
Hello, World! #3
error #3
>>> try:
... f4( "Hello, World! #4" )
... except:
... print( "error #4" )
...
Pre-processing is done here('mytest2',) {} ('Hello, World! #4',) {}
Hello, World! #4
error #4
>>>
"""
#Click here for decorators that clearly take no arguments
# _my_Rename decorator and define globally
def _my_decorator( func ):
# _my_decorator_body()If there is any necessary processing before defining, write it here
print( "_my_decorator_body()If there is any necessary processing before defining, write it here" )
@wraps(func)
def _my_decorator_body( *body_args, **body_kwargs ):
#Pre-processing is done here
print( "Pre-processing is done here", args, kwargs, body_args, body_kwargs )
try:
#Execution of the decorated body
ret = func( *body_args, **body_kwargs )
except:
raise
#Post-processing is done here
print( "Post-processing is done here", args, kwargs, body_args, body_kwargs )
return ret
#Write here if processing is required when the decorator is listed#2
print( "Write here if processing is required when the decorator is listed#2" )
return _my_decorator_body
#This is the end of the decorator that clearly takes no arguments
#Write here if processing is required when the decorator is listed#1
print( "Write here if processing is required when the decorator is listed#1" )
if len(args) == 1 and callable( args[0] ):
#If the decorator is called with no arguments, handle it here
print( "No arguments" )
return _my_decorator( args[0] )
else:
#If the decorator is called with arguments, handle it here
print( "There are some arguments:", args )
return _my_decorator
###########################################
# unitttest
###########################################
import unittest
from io import StringIO
import sys
class Test_My_Decorator(unittest.TestCase):
def setUp(self):
self.saved_stdout = sys.stdout
self.stdout = StringIO()
sys.stdout = self.stdout
def tearDown(self):
sys.stdout = self.saved_stdout
def test_decorator_noarg(self):
@my_decorator
def t1(arg0):
print( arg0 )
t1("test_decorator_noarg")
import re
s = re.sub('0x[0-9a-f]+', '0x', self.stdout.getvalue())
self.assertEqual(s,
"Write here if processing is required when the decorator is listed#1\n" +
"No arguments\n" +
"_my_decorator_body()If there is any necessary processing before defining, write it here\n" +
"Write here if processing is required when the decorator is listed#2\n" +
"Pre-processing is done here(<function Test_My_Decorator.test_decorator_noarg.<locals>.t1 at 0x>,) {} ('test_decorator_noarg',) {}\n" +
"test_decorator_noarg\n" +
"Post-processing is done here(<function Test_My_Decorator.test_decorator_noarg.<locals>.t1 at 0x>,) {} ('test_decorator_noarg',) {}\n"
)
def test_decorator_witharg(self):
@my_decorator('with arg')
def t1(arg0):
print( arg0 )
t1("test_decorator_witharg")
self.assertEqual(self.stdout.getvalue(),
"Write here if processing is required when the decorator is listed#1\n" +
"There are some arguments: ('with arg',)\n" +
"_my_decorator_body()If there is any necessary processing before defining, write it here\n" +
"Write here if processing is required when the decorator is listed#2\n" +
"Pre-processing is done here('with arg',) {} ('test_decorator_witharg',) {}\n" +
"test_decorator_witharg\n" +
"Post-processing is done here('with arg',) {} ('test_decorator_witharg',) {}\n"
)
def test_functionname(self):
@my_decorator
def t1():
return t1.__name__
f_name = t1()
self.assertEqual( f_name, "t1" )
def test_docattribute(self):
@my_decorator
def t1():
"""Test Document"""
pass
self.assertEqual( t1.__doc__, "Test Document" )
###########################################
# main
###########################################
if __name__ == '__main__':
@my_decorator
def f1( arg1 ):
print( arg1 )
@my_decorator('mytest1')
def f2( arg2 ):
print( arg2 )
@my_decorator
def f3( arg1 ):
print( arg1 )
a = 1/0
@my_decorator('mytest2')
def f4( arg2 ):
print( arg2 )
a = 1/0
try:
f1( "Hello, World! #1" )
except:
print( "error #1" )
try:
f2( "Hello, World! #2" )
except:
print( "error #2" )
try:
f3( "Hello, World! #3" )
except:
print( "error #3" )
try:
f4( "Hello, World! #4" )
except:
print( "error #4" )
import doctest
doctest.testmod(optionflags=doctest.ELLIPSIS)
unittest.main()
Sample execution
$ python decorator_framework.py
Write here if processing is required when the decorator is listed#1
No arguments
_my_decorator_body()If there is any necessary processing before defining, write it here
Write here if processing is required when the decorator is listed#2
Write here if processing is required when the decorator is listed#1
There are some arguments: ('mytest1',)
_my_decorator_body()If there is any necessary processing before defining, write it here
Write here if processing is required when the decorator is listed#2
Write here if processing is required when the decorator is listed#1
No arguments
_my_decorator_body()If there is any necessary processing before defining, write it here
Write here if processing is required when the decorator is listed#2
Write here if processing is required when the decorator is listed#1
There are some arguments: ('mytest2',)
_my_decorator_body()If there is any necessary processing before defining, write it here
Write here if processing is required when the decorator is listed#2
Pre-processing is done here(<function f1 at 0x76973a08>,) {} ('Hello, World! #1',) {}
Hello, World! #1
Post-processing is done here(<function f1 at 0x76973a08>,) {} ('Hello, World! #1',) {}
Pre-processing is done here('mytest1',) {} ('Hello, World! #2',) {}
Hello, World! #2
Post-processing is done here('mytest1',) {} ('Hello, World! #2',) {}
Pre-processing is done here(<function f3 at 0x7685bd20>,) {} ('Hello, World! #3',) {}
Hello, World! #3
error #3
Pre-processing is done here('mytest2',) {} ('Hello, World! #4',) {}
Hello, World! #4
error #4
....
----------------------------------------------------------------------
Ran 4 tests in 0.005s
OK
$
unittest
$ python -m unittest -v decorator_framework.py
test_decorator_noarg (decorator_framework.Test_My_Decorator) ... ok
test_decorator_witharg (decorator_framework.Test_My_Decorator) ... ok
test_docattribute (decorator_framework.Test_My_Decorator) ... ok
test_functionname (decorator_framework.Test_My_Decorator) ... ok
----------------------------------------------------------------------
Ran 4 tests in 0.006s
OK
$
Recommended Posts