Home > Article > Backend Development > Summary of the use of decorators in Python
I am learning Python recently. The following is what was introduced in the Python learning group. Learning now and practicing more is a good way to learn. I hope everyone will like it.
Python has a lot of powerful and considerate features. , if you want to list the most popular rankings, then decorators will definitely be among them.
When you first meet the decorator, you will feel elegant and magical. But when you want to realize it with your own hands, you always feel a sense of distance, just like the ice beauty in the purdah. This is often because other concepts are mixed together when understanding decorators. When I remove the layers of veil, you will see that pure decoration is actually quite simple and straightforward.
The principle of decorator
Run an example of decorator under the interpreter to feel it intuitively.
# make_bold is the decorator, the implementation method is omitted here
>>> @make_bold ... def get_content(): ... return 'hello world' ... >>> get_content() '<b>hello world</b>'
get_content decorated by make_bold, the result returned after the call will be automatically wrapped by the b tag . How to do it? You can understand it in 4 simple steps.
1. The function is an object
We define a get_content function. At this time, get_content is also an object, and it can perform operations on all objects.
def get_content(): return 'hello world'
It has id, type and value.
>>> id(get_content) 140090200473112 >>> type(get_content) <class 'function'> >>> get_content <function get_content at 0x7f694aa2be18>
can be assigned to other variables like other objects.
>>> func_name = get_content >>> func_name() 'hello world'
It can be passed as a parameter or as a return value
>>> def foo(bar): ... print(bar()) ... return bar ... >>> func = foo(get_content) hello world >>> func() 'hello world'
2 . Custom functionObject
We can use class to construct the function object. The function object that has the member function call is the function object that is called when the function object is called.
class FuncObj(object): def init(self, name): print('Initialize') self.name= name def call(self): print('Hi', self.name)
Let’s call it. As you can see, the use of function objects is divided into two steps: construction and calling (students, please pay attention, this is the test point).
>>> fo = FuncObj('python') Initialize >>> fo() Hi python
3. @ is a syntax sugar
The @ of the decorator does nothing special and the same function can be achieved without it , it just requires more code.
@make_bold def get_content(): return 'hello world' # 上面的代码等价于下面的 def get_content(): return 'hello world' get_content = make_bold(get_content)
make_bold is a function, requiring the input parameter to be a function object and the return value to be a function object. The syntactic sugar of @ actually eliminates the last line of code above, making it more readable. After using the decorator, every time get_content is called, the function object returned by make_bold is actually called.
4. Use classes to implement decorators
The input parameter is a function object, and the return parameter is a function object. If the constructor of the class in step 2 is changed to a function object whose input parameter is a function object, wouldn’t it work? Does it fit the bill exactly? Let’s try implementing make_bold.
class make_bold(object): def init(self, func): print('Initialize') self.func = func def call(self): print('Call') return '<b>{}</b>'.format(self.func())
It’s done, let’s see if it works.
>>> @make_bold ... def get_content(): ... return 'hello world' ... Initialize >>> get_content() Call '<b>hello world</b>'
Successfully implemented the decorator! Is not it simple?
Here is an analysis of the two processes of construction and calling that were emphasized previously. Let’s remove the @ syntax sugar to make it easier to understand.
# Construction, when using the decorator to construct the function object, call init
>>> get_content = make_bold(get_content) Initialize # 调用,实际上直接调用的是make_bold构造出来的函数对象 >>> get_content() Call '<b>hello world</b>'
It is completely clear at this point, the flowers are finished, and you can turn it off The web page~~~(If you just want to know the principle of decorator)
Function version of the decorator
When reading the source code, we often see the implementation using nested functions Decorator, what do you understand? It only takes 4 steps.
1. Function object initialization of def
It is easy to see when the function object implemented with class is constructed, but when is the function object defined by def constructed?
# The global variables here have deleted irrelevant content
>>> globals() {} >>> def func(): ... pass ... >>> globals() {'func': <function func at 0x10f5baf28>}
Unlike some compiled languages, the function has already been constructed when the program starts. . As you can see from the above example, a function object is constructed until def is executed and assigned to the variable make_bold.
The effect of this code is very similar to the code below.
class NoName(object): def call(self): pass func = NoName()
2. Nested functions
Python functions can be nested definitions.
def outer(): print('Before def:', locals()) def inner(): pass print('After def:', locals()) return inner
inner is defined within outer, so it is counted as a local variable of outer. The function object is not created until def inner is executed, so each time outer is called, a new inner will be created. As can be seen below, the inner returned each time is different.
>>> outer() Before def: {} After def: {'inner': <function outer.<locals>.inner at 0x7f0b18fa0048>} <function outer.<locals>.inner at 0x7f0b18fa0048> >>> outer() Before def: {} After def: {'inner': <function outer.<locals>.inner at 0x7f0b18fa00d0>} <function outer.<locals>.inner at 0x7f0b18fa00d0>
3. Closure
What’s special about nested functions? Because there is closure.
def outer(): msg = 'hello world' def inner(): print(msg) return inner
The following test shows that inner can access the local variable msg of outer.
>>> func = outer() >>> func() hello world
Closures have two characteristics
1. inner can access variables (local variables, ##) in the namespace of outer and its ancestor functions #Function parameters). 2. The call to outer has returned, but its namespace is referenced by the returned inner object, so it will not be recycled yet.
def make_bold(func): print('Initialize') def wrapper(): print('Call') return '<b>{}</b>'.format(func()) return wrapper
用法跟类实现的装饰器一样。可以去掉 @ 语法糖分析下 构造 和 调用 的时机。
>>> @make_bold ... def get_content(): ... return 'hello world' ... Initialize >>> get_content() Call '<b>hello world</b>'
因为返回的 wrapper 还在引用着,所以存在于 make_bold 命名空间的 func 不会消失。 make_bold 可以装饰多个函数, wrapper 不会调用混淆,因为每次调用 make_bold ,都会有创建新的命名空间和新的 wrapper 。
到此函数实现装饰器也理清楚了,完结撒花,可以关掉网页了~~~(后面是使用装饰的常见问题)
常见问题
1. 怎么实现带参数的装饰器?
带参数的装饰器,有时会异常的好用。我们看个例子。
>>> @make_header(2) ... def get_content(): ... return 'hello world' ... >>> get_content() '<h2>hello world</h2>'
怎么做到的呢?其实这跟装饰器语法没什么关系。去掉 @ 语法糖会变得很容易理解。
@make_header(2) def get_content(): return 'hello world' # 等价于 def get_content(): return 'hello world' unnamed_decorator = make_header(2) get_content = unnamed_decorator(get_content)
上面代码中的 unnamed_decorator 才是真正的装饰器, make_header 是个普通的函数,它的返回值是装饰器。
来看一下实现的代码。
def make_header(level): print('Create decorator') # 这部分跟通常的装饰器一样,只是wrapper通过闭包访问了变量level def decorator(func): print('Initialize') def wrapper(): print('Call') return '<h{0}>{1}</h{0}>'.format(level, func()) return wrapper # make_header返回装饰器 return decorator
看了实现代码,装饰器的 构造 和 调用 的时序已经很清楚了。
>>> @make_header(2) ... def get_content(): ... return 'hello world' ... Create decorator Initialize >>> get_content() Call '<h2>hello world</h2>'
2. 如何装饰有参数的函数?
为了有条理地理解装饰器,之前例子里的被装饰函数有意设计成无参的。我们来看个例子。
@make_bold def get_login_tip(name): return 'Welcome back, {}'.format(name)
最直接的想法是把 get_login_tip 的参数透传下去。
class make_bold(object): def init(self, func): self.func = func def call(self, name): return '<b>{}</b>'.format(self.func(name))
如果被装饰的函数参数是明确固定的,这么写是没有问题的。但是 make_bold 明显不是这种场景。它既需要装饰没有参数的 get_content ,又需要装饰有参数的 get_login_tip 。这时候就需要可变参数了。
class make_bold(object): def init(self, func): self.func = func def call(self, *args, **kwargs): return '<b>{}</b>'.format(self.func(*args, **kwargs))
当装饰器不关心被装饰函数的参数,或是被装饰函数的参数多种多样的时候,可变参数非常合适。可变参数不属于装饰器的语法内容,这里就不深入探讨了。
3. 一个函数能否被多个装饰器装饰?
下面这么写合法吗?
@make_italic @make_bold def get_content(): return 'hello world'
合法。上面的的代码和下面等价,留意一下装饰的顺序。
def get_content(): return 'hello world' get_content = make_bold(get_content) # 先装饰离函数定义近的 get_content = make_italic(get_content)
4. functools.wraps 有什么用?
Python的装饰器倍感贴心的地方是对调用方透明。调用方完全不知道也不需要知道调用的函数被装饰了。这样我们就能在调用方的代码完全不改动的前提下,给函数patch功能。
为了对调用方透明,装饰器返回的对象要伪装成被装饰的函数。伪装得越像,对调用方来说差异越小。有时光伪装函数名和参数是不够的,因为Python的函数对象有一些元信息调用方可能读取了。为了连这些元信息也伪装上, functools.wraps 出场了。它能用于把被调用函数的 module , name , qualname , doc , annotations 赋值给装饰器返回的函数对象。
import functools def make_bold(func): @functools.wraps(func) def wrapper(*args, **kwargs): return '<b>{}</b>'.format(func(*args, **kwargs)) return wrapper
对比一下效果。
>>> @make_bold ... def get_content(): ... '''Return page content''' ... return 'hello world' # 不用functools.wraps的结果 >>> get_content.name 'wrapper' >>> get_content.doc >>> # 用functools.wraps的结果 >>> get_content.name 'get_content' >>> get_content.doc 'Return page content'
实现装饰器时往往不知道调用方会怎么用,所以养成好习惯加上 functools.wraps 吧。
这次是真·完结了,撒花吧~~~
The above is the detailed content of Summary of the use of decorators in Python. For more information, please follow other related articles on the PHP Chinese website!