Skip to content

Commit b959e94

Browse files
committed
9.9小节完成~
1 parent 00125e6 commit b959e94

File tree

3 files changed

+185
-3
lines changed

3 files changed

+185
-3
lines changed

cookbook/c09/p09_class_decorator.py

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
#!/usr/bin/env python
2+
# -*- encoding: utf-8 -*-
3+
"""
4+
Topic: 将装饰器定义成类
5+
Desc :
6+
"""
7+
8+
import types
9+
from functools import wraps
10+
11+
class Profiled:
12+
def __init__(self, func):
13+
wraps(func)(self)
14+
self.ncalls = 0
15+
16+
def __call__(self, *args, **kwargs):
17+
self.ncalls += 1
18+
return self.__wrapped__(*args, **kwargs)
19+
20+
# def __get__(self, instance, cls):
21+
# if instance is None:
22+
# return self
23+
# else:
24+
# return types.MethodType(self, instance)
25+
26+
@Profiled
27+
def add(x, y):
28+
return x + y
29+
30+
class Spam:
31+
@Profiled
32+
def bar(self, x):
33+
print(self, x)
34+
35+
Spam().bar(3)
36+
37+
38+
import types
39+
from functools import wraps
40+
41+
def profiled(func):
42+
ncalls = 0
43+
@wraps(func)
44+
def wrapper(*args, **kwargs):
45+
nonlocal ncalls
46+
ncalls += 1
47+
return func(*args, **kwargs)
48+
wrapper.ncalls = lambda: ncalls
49+
return wrapper
50+
51+
# Example
52+
@profiled
53+
def add(x, y):
54+
return x + y
55+

source/c09/p09_define_decorators_as_classes.rst

Lines changed: 130 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,141 @@
55
----------
66
问题
77
----------
8-
todo...
8+
你想使用一个装饰器去包装函数,但是希望返回一个可调用的实例。
9+
你需要让你的装饰器可以同时工作在类定义的内部和外部。
10+
11+
|
912
1013
----------
1114
解决方案
1215
----------
13-
todo...
16+
为了将装饰器定义成一个实例,你需要确保它实现了 ``__call__()`` 和 ``__get__()`` 方法。
17+
例如,下面的代码定义了一个类,它在其他函数上放置一个简单的记录层:
18+
19+
.. code-block:: python
20+
21+
import types
22+
from functools import wraps
23+
24+
class Profiled:
25+
def __init__(self, func):
26+
wraps(func)(self)
27+
self.ncalls = 0
28+
29+
def __call__(self, *args, **kwargs):
30+
self.ncalls += 1
31+
return self.__wrapped__(*args, **kwargs)
32+
33+
def __get__(self, instance, cls):
34+
if instance is None:
35+
return self
36+
else:
37+
return types.MethodType(self, instance)
38+
39+
你可以将它当做一个普通的装饰器来使用,在类里面或外面都可以:
40+
41+
.. code-block:: python
42+
43+
@Profiled
44+
def add(x, y):
45+
return x + y
46+
47+
class Spam:
48+
@Profiled
49+
def bar(self, x):
50+
print(self, x)
51+
52+
在交互环境中的使用示例:
53+
54+
.. code-block:: python
55+
56+
>>> add(2, 3)
57+
5
58+
>>> add(4, 5)
59+
9
60+
>>> add.ncalls
61+
2
62+
>>> s = Spam()
63+
>>> s.bar(1)
64+
<__main__.Spam object at 0x10069e9d0> 1
65+
>>> s.bar(2)
66+
<__main__.Spam object at 0x10069e9d0> 2
67+
>>> s.bar(3)
68+
<__main__.Spam object at 0x10069e9d0> 3
69+
>>> Spam.bar.ncalls
70+
3
71+
72+
|
1473
1574
----------
1675
讨论
1776
----------
18-
todo...
77+
将装饰器定义成类通常是很简单的。但是这里还是有一些细节需要解释下,特别是当你想将它作用在实例方法上的时候。
78+
79+
首先,使用 ``functools.wraps()`` 函数的作用跟之前还是一样,将被包装函数的元信息复制到可调用实例中去。
80+
81+
其次,通常很容易会忽视上面的 ``__get__()`` 方法。如果你忽略它,保持其他代码不变再次运行,
82+
你会发现当你去调用被装饰实例方法时出现很奇怪的问题。例如:
83+
84+
.. code-block:: python
85+
86+
>>> s = Spam()
87+
>>> s.bar(3)
88+
Traceback (most recent call last):
89+
...
90+
TypeError: bar() missing 1 required positional argument: 'x'
91+
92+
出错原因是当方法函数在一个类中被查找时,它们的 ``__get__()`` 方法依据描述器协议被调用,
93+
在8.9小节已经讲述过描述器协议了。在这里,``__get__()`` 的目的是创建一个绑定方法对象
94+
(最终会给这个方法传递self参数)。下面是一个例子来演示底层原理:
95+
96+
.. code-block:: python
97+
98+
>>> s = Spam()
99+
>>> def grok(self, x):
100+
... pass
101+
...
102+
>>> grok.__get__(s, Spam)
103+
<bound method Spam.grok of <__main__.Spam object at 0x100671e90>>
104+
>>>
105+
106+
``__get__()`` 方法是为了确保绑定方法对象能被正确的创建。
107+
``type.MethodType()`` 手动创建一个绑定方法来使用。只有当实例被使用的时候绑定方法才会被创建。
108+
如果这个方法是在类上面来访问,
109+
那么 ``__get__()`` 中的instance参数会被设置成None并直接返回 ``Profiled`` 实例本身。
110+
这样的话我们就可以提取它的 ``ncalls`` 属性了。
111+
112+
如果你想避免一些混乱,也可以考虑另外一个使用闭包和 ``nonlocal`` 变量实现的装饰器,这个在9.5小节有讲到。例如:
113+
114+
.. code-block:: python
115+
116+
import types
117+
from functools import wraps
118+
119+
def profiled(func):
120+
ncalls = 0
121+
@wraps(func)
122+
def wrapper(*args, **kwargs):
123+
nonlocal ncalls
124+
ncalls += 1
125+
return func(*args, **kwargs)
126+
wrapper.ncalls = lambda: ncalls
127+
return wrapper
128+
129+
# Example
130+
@profiled
131+
def add(x, y):
132+
return x + y
133+
134+
这个方式跟之前的效果几乎一样,除了对于 ``ncalls`` 的访问现在是通过一个被绑定为属性的函数来实现,例如:
135+
136+
.. code-block:: python
137+
138+
>>> add(2, 3)
139+
5
140+
>>> add(4, 5)
141+
9
142+
>>> add.ncalls()
143+
2
144+
>>>
145+

0 commit comments

Comments
 (0)