Skip to content

Commit a995124

Browse files
committed
8.9小节完成~
1 parent ed3c43f commit a995124

File tree

2 files changed

+237
-4
lines changed

2 files changed

+237
-4
lines changed

cookbook/c08/p09_descriptor.py

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
#!/usr/bin/env python
2+
# -*- encoding: utf-8 -*-
3+
"""
4+
Topic: 通过描述器定义新的实例属性
5+
Desc :
6+
"""
7+
8+
# Descriptor attribute for an integer type-checked attribute
9+
class Integer:
10+
def __init__(self, name):
11+
self.name = name
12+
13+
def __get__(self, instance, cls):
14+
if instance is None:
15+
return self
16+
else:
17+
return instance.__dict__[self.name]
18+
19+
def __set__(self, instance, value):
20+
if not isinstance(value, int):
21+
raise TypeError('Expected an int')
22+
instance.__dict__[self.name] = value
23+
24+
def __delete__(self, instance):
25+
del instance.__dict__[self.name]
26+
27+
28+
class Point:
29+
x = Integer('x')
30+
y = Integer('y')
31+
32+
def __init__(self, x, y):
33+
self.x = x
34+
self.y = y
35+
36+
37+
p = Point(2, 3)
38+
print(p.x)
39+
p.y = 5
40+
41+
42+
# Descriptor for a type-checked attribute
43+
class Typed:
44+
def __init__(self, name, expected_type):
45+
self.name = name
46+
self.expected_type = expected_type
47+
48+
def __get__(self, instance, cls):
49+
if instance is None:
50+
return self
51+
else:
52+
return instance.__dict__[self.name]
53+
54+
def __set__(self, instance, value):
55+
if not isinstance(value, self.expected_type):
56+
raise TypeError('Expected ' + str(self.expected_type))
57+
instance.__dict__[self.name] = value
58+
59+
def __delete__(self, instance):
60+
del instance.__dict__[self.name]
61+
62+
63+
# Class decorator that applies it to selected attributes
64+
def typeassert(**kwargs):
65+
def decorate(cls):
66+
for name, expected_type in kwargs.items():
67+
# Attach a Typed descriptor to the class
68+
setattr(cls, name, Typed(name, expected_type))
69+
return cls
70+
71+
return decorate
72+
73+
74+
# Example use
75+
@typeassert(name=str, shares=int, price=float)
76+
class Stock:
77+
def __init__(self, name, shares, price):
78+
self.name = name
79+
self.shares = shares
80+
self.price = price
81+
Lines changed: 156 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,170 @@
11
============================
2-
8.9 创建新的类或者实例属性
2+
8.9 创建新的类或实例属性
33
============================
44

55
----------
66
问题
77
----------
8-
todo...
8+
你想创建一个新的拥有一些额外功能的实例属性类型,比如类型检查。
9+
10+
|
911
1012
----------
1113
解决方案
1214
----------
13-
todo...
15+
如果你想创建一个全新的实例属性,可以通过一个描述器类的形式来定义它的功能。下面是一个例子:
16+
17+
.. code-block:: python
18+
19+
# Descriptor attribute for an integer type-checked attribute
20+
class Integer:
21+
def __init__(self, name):
22+
self.name = name
23+
24+
def __get__(self, instance, cls):
25+
if instance is None:
26+
return self
27+
else:
28+
return instance.__dict__[self.name]
29+
30+
def __set__(self, instance, value):
31+
if not isinstance(value, int):
32+
raise TypeError('Expected an int')
33+
instance.__dict__[self.name] = value
34+
35+
def __delete__(self, instance):
36+
del instance.__dict__[self.name]
37+
38+
一个描述器就是一个实现了三个核心的属性访问操作(get, set, delete)的类,
39+
分别为 ``__get__()`` 、``__set__()`` 和 ``__delete__()`` 这三个特殊的方法。
40+
这些方法接受一个实例作为输入,之后相应的操作实例底层的字典。
41+
42+
为了使用一个描述器,需将这个描述器的实例作为类属性放到一个类的定义中。例如:
43+
44+
.. code-block:: python
45+
46+
class Point:
47+
x = Integer('x')
48+
y = Integer('y')
49+
50+
def __init__(self, x, y):
51+
self.x = x
52+
self.y = y
53+
54+
当你这样做后,所有队描述器属性(比如x或y)的访问会被
55+
``__get__()`` 、``__set__()`` 和 ``__delete__()`` 方法捕获到。例如:
56+
57+
.. code-block:: python
58+
59+
>>> p = Point(2, 3)
60+
>>> p.x # Calls Point.x.__get__(p,Point)
61+
2
62+
>>> p.y = 5 # Calls Point.y.__set__(p, 5)
63+
>>> p.x = 2.3 # Calls Point.x.__set__(p, 2.3)
64+
Traceback (most recent call last):
65+
File "<stdin>", line 1, in <module>
66+
File "descrip.py", line 12, in __set__
67+
raise TypeError('Expected an int')
68+
TypeError: Expected an int
69+
>>>
70+
71+
作为输入,描述器的每一个方法会接受一个操作实例。
72+
为了实现请求操作,会相应的操作实例底层的字典(__dict__属性)。
73+
描述器的 ``self.name`` 属性存储了在实例字典中被实际使用到的key。
74+
75+
|
1476
1577
----------
1678
讨论
1779
----------
18-
todo...
80+
描述器可实现大部分Python类特性中的底层魔法,
81+
包括 ``@classmethod`` 、``@staticmethod`` 、``@property`` ,甚至是 ``__slots__`` 特性。
82+
83+
通过定义一个描述器,你可以在底层捕获核心的实例操作(get, set, delete),并且可完全自定义它们的行为。
84+
这是一个强大的工具,有了它你可以实现很多高级功能,并且它也是很多高级库和框架中的重要工具之一。
85+
86+
描述器的一个比较困惑的地方是它只能在类级别被定义,而不能为每个实例单独定义。因此,下面的代码是无法工作的:
87+
88+
.. code-block:: python
89+
90+
# Does NOT work
91+
class Point:
92+
def __init__(self, x, y):
93+
self.x = Integer('x') # No! Must be a class variable
94+
self.y = Integer('y')
95+
self.x = x
96+
self.y = y
97+
98+
同时,``__get__()`` 方法实现起来比看上去要复杂得多:
99+
100+
.. code-block:: python
101+
102+
# Descriptor attribute for an integer type-checked attribute
103+
class Integer:
104+
105+
def __get__(self, instance, cls):
106+
if instance is None:
107+
return self
108+
else:
109+
return instance.__dict__[self.name]
110+
111+
``__get__()`` 看上去有点复杂的原因归结于实例变量和类变量的不同。
112+
如果一个描述器被当做一个类变量来访问,那么 ``instance`` 参数被设置成 ``None`` 。
113+
这种情况下,标准做法就是简单的返回这个描述器本身即可(尽管你还可以添加其他的自定义操作)。例如:
114+
115+
.. code-block:: python
116+
117+
>>> p = Point(2,3)
118+
>>> p.x # Calls Point.x.__get__(p, Point)
119+
2
120+
>>> Point.x # Calls Point.x.__get__(None, Point)
121+
<__main__.Integer object at 0x100671890>
122+
>>>
123+
124+
125+
描述器通常是那些使用到装饰器或元类的大型框架中的一个组件。同时它们的使用也被隐藏在后面。
126+
举个例子,下面是一些更高级的基于描述器的代码,并涉及到一个类装饰器:
127+
128+
.. code-block:: python
129+
130+
# Descriptor for a type-checked attribute
131+
class Typed:
132+
def __init__(self, name, expected_type):
133+
self.name = name
134+
self.expected_type = expected_type
135+
def __get__(self, instance, cls):
136+
if instance is None:
137+
return self
138+
else:
139+
return instance.__dict__[self.name]
140+
141+
def __set__(self, instance, value):
142+
if not isinstance(value, self.expected_type):
143+
raise TypeError('Expected ' + str(self.expected_type))
144+
instance.__dict__[self.name] = value
145+
def __delete__(self, instance):
146+
del instance.__dict__[self.name]
147+
148+
# Class decorator that applies it to selected attributes
149+
def typeassert(**kwargs):
150+
def decorate(cls):
151+
for name, expected_type in kwargs.items():
152+
# Attach a Typed descriptor to the class
153+
setattr(cls, name, Typed(name, expected_type))
154+
return cls
155+
return decorate
156+
157+
# Example use
158+
@typeassert(name=str, shares=int, price=float)
159+
class Stock:
160+
def __init__(self, name, shares, price):
161+
self.name = name
162+
self.shares = shares
163+
self.price = price
164+
165+
最后要指出的一点是,如果你只是想简单的自定义某个类的单个属性访问的话就不用去写描述器了。
166+
这种情况下使用8.6小节介绍的property技术会更加容易。
167+
当程序中有很多重复代码的时候描述器就很有用了
168+
(比如你想在你代码的很多地方使用描述器提供的功能或者将它作为一个函数库特性)。
169+
170+

0 commit comments

Comments
 (0)