Skip to content

Commit b2ca55e

Browse files
committed
8.6小节完成~
1 parent 74a5e35 commit b2ca55e

File tree

2 files changed

+299
-3
lines changed

2 files changed

+299
-3
lines changed

cookbook/c08/p06_managed_attribute.py

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
#!/usr/bin/env python
2+
# -*- encoding: utf-8 -*-
3+
"""
4+
Topic: 可管理的属性
5+
Desc :
6+
"""
7+
import math
8+
9+
10+
class Person:
11+
def __init__(self, first_name):
12+
self.first_name = first_name
13+
14+
# Getter function
15+
@property
16+
def first_name(self):
17+
return self._first_name
18+
19+
# Setter function
20+
@first_name.setter
21+
def first_name(self, value):
22+
if not isinstance(value, str):
23+
raise TypeError('Expected a string')
24+
self._first_name = value
25+
26+
# Deleter function (optional)
27+
@first_name.deleter
28+
def first_name(self):
29+
raise AttributeError("Can't delete attribute")
30+
31+
32+
a = Person('Guido')
33+
print(a.first_name) # Calls the getter
34+
# a.first_name = 42 # Calls the setter
35+
# del a.first_name # Calls the deleter
36+
37+
38+
class Person1:
39+
def __init__(self, first_name):
40+
self.set_first_name(first_name)
41+
42+
# Getter function
43+
def get_first_name(self):
44+
return self._first_name
45+
46+
# Setter function
47+
def set_first_name(self, value):
48+
if not isinstance(value, str):
49+
raise TypeError('Expected a string')
50+
self._first_name = value
51+
52+
# Deleter function (optional)
53+
def del_first_name(self):
54+
raise AttributeError("Can't delete attribute")
55+
56+
# Make a property from existing get/set methods
57+
name = property(get_first_name, set_first_name, del_first_name)
58+
59+
60+
print(Person1.name.fget)
61+
print(Person1.name.fset)
62+
print(Person1.name.fdel)
63+
64+
65+
class Circle:
66+
"""动态计算的property"""
67+
68+
def __init__(self, radius):
69+
self.radius = radius
70+
71+
@property
72+
def diameter(self):
73+
return self.radius ** 2
74+
75+
@property
76+
def perimeter(self):
77+
return 2 * math.pi * self.radius
78+
79+
@property
80+
def area(self):
81+
return math.pi * self.radius ** 2
82+
83+
84+
c = Circle(4.0)
85+
print(c.radius)
86+
print(c.area) # Notice lack of ()

source/c08/p06_create_managed_attributes.rst

Lines changed: 213 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,224 @@
55
----------
66
问题
77
----------
8-
todo...
8+
你想给某个实例attribute增加除访问与修改之外的其他处理逻辑,比如类型检查或合法性验证。
9+
10+
|
911
1012
----------
1113
解决方案
1214
----------
13-
todo...
15+
自定义某个属性的一种简单方法是将它定义为一个property。
16+
例如,下面的代码定义了一个property,增加对一个属性简单的类型检查:
17+
18+
.. code-block:: python
19+
20+
class Person:
21+
def __init__(self, first_name):
22+
self.first_name = first_name
23+
24+
# Getter function
25+
@property
26+
def first_name(self):
27+
return self._first_name
28+
29+
# Setter function
30+
@first_name.setter
31+
def first_name(self, value):
32+
if not isinstance(value, str):
33+
raise TypeError('Expected a string')
34+
self._first_name = value
35+
36+
# Deleter function (optional)
37+
@first_name.deleter
38+
def first_name(self):
39+
raise AttributeError("Can't delete attribute")
40+
41+
上述代码中有三个相关联的方法,这三个方法的名字都必须一样。
42+
第一个方法是一个 ``getter`` 函数,它使得 ``first_name`` 成为一个属性。
43+
其他两个方法给 ``first_name`` 属性添加了 ``setter`` 和 ``deleter`` 函数。
44+
需要强调的是只有在 ``first_name`` 属性被创建后,
45+
后面的两个装饰器 ``@first_name.setter`` 和 ``@first_name.deleter`` 才能被定义。
46+
47+
property的一个关键特征是它看上去跟普通的attribute没什么两样,
48+
但是访问它的时候会自动触发 ``getter`` 、``setter`` 和 ``deleter`` 方法。例如:
49+
50+
.. code-block:: python
51+
52+
>>> a = Person('Guido')
53+
>>> a.first_name # Calls the getter
54+
'Guido'
55+
>>> a.first_name = 42 # Calls the setter
56+
Traceback (most recent call last):
57+
File "<stdin>", line 1, in <module>
58+
File "prop.py", line 14, in first_name
59+
raise TypeError('Expected a string')
60+
TypeError: Expected a string
61+
>>> del a.first_name
62+
Traceback (most recent call last):
63+
File "<stdin>", line 1, in <module>
64+
AttributeError: can't delete attribute
65+
>>>
66+
67+
在实现一个property的时候,底层数据(如果有的话)仍然需要存储在某个地方。
68+
因此,在get和set方法中,你会看到对 ``_first_name``属性的操作,这也是实际数据保存的地方。
69+
另外,你可能还会问为什么 ``__init__()`` 方法中设置了 ``self.first_name`` 而不是 ``self._first_name`` 。
70+
在这个例子中,我们创建一个property的目的就是在设置attribute的时候进行检查。
71+
因此,你可能想在初始化的时候也进行这种类型检查。通过设置 ``self.first_name`` ,自动调用 ``setter`` 方法,
72+
这个方法里面会进行参数的检查,否则就是直接访问 ``self._first_name`` 了。
73+
74+
还能在已存在的get和set方法基础上定义property。例如:
75+
76+
.. code-block:: python
77+
78+
class Person:
79+
def __init__(self, first_name):
80+
self.set_first_name(first_name)
81+
82+
# Getter function
83+
def get_first_name(self):
84+
return self._first_name
85+
86+
# Setter function
87+
def set_first_name(self, value):
88+
if not isinstance(value, str):
89+
raise TypeError('Expected a string')
90+
self._first_name = value
91+
92+
# Deleter function (optional)
93+
def del_first_name(self):
94+
raise AttributeError("Can't delete attribute")
95+
96+
# Make a property from existing get/set methods
97+
name = property(get_first_name, set_first_name, del_first_name)
98+
99+
|
14100
15101
----------
16102
讨论
17103
----------
18-
todo...
104+
一个property属性其实就是一系列相关绑定方法的集合。如果你去查看拥有property的类,
105+
就会发现property本身的fget、fset和fdel属性就是类里面的普通方法。比如:
106+
107+
.. code-block:: python
108+
109+
>>> Person.first_name.fget
110+
<function Person.first_name at 0x1006a60e0>
111+
>>> Person.first_name.fset
112+
<function Person.first_name at 0x1006a6170>
113+
>>> Person.first_name.fdel
114+
<function Person.first_name at 0x1006a62e0>
115+
>>>
116+
117+
通常来讲,你不会直接取调用fget或者fset,它们会在访问property的时候自动被触发。
118+
119+
只有当你确实需要对attribute执行其他额外的操作的时候才应该使用到property。
120+
有时候一些从其他编程语言(比如Java)过来的程序员总认为所有访问都应该通过getter和setter,
121+
所以他们认为代码应该像下面这样写:
122+
123+
.. code-block:: python
124+
125+
class Person:
126+
def __init__(self, first_name):
127+
self.first_name = name
128+
129+
@property
130+
def first_name(self):
131+
return self._first_name
132+
133+
@first_name.setter
134+
def first_name(self, value):
135+
self._first_name = value
136+
137+
不要写这种没有做任何其他额外操作的property。
138+
首先,它会让你的代码变得很臃肿,并且还会迷惑阅读者。
139+
其次,它还会让你的程序运行起来变慢很多。
140+
最后,这样的设计并没有带来任何的好处。
141+
特别是当你以后想给普通attribute访问添加额外的处理逻辑的时候,
142+
你可以将它变成一个property而无需改变原来的代码。
143+
因为访问attribute的代码还是保持原样。
144+
145+
Properties还是一种定义动态计算attribute的方法。
146+
这种类型的attributes并不会被实际的存储,而是在需要的时候计算出来。比如:
147+
148+
.. code-block:: python
149+
150+
import math
151+
class Circle:
152+
def __init__(self, radius):
153+
self.radius = radius
154+
155+
@property
156+
def area(self):
157+
return math.pi * self.radius ** 2
158+
159+
@property
160+
def diameter(self):
161+
return self.radius ** 2
162+
163+
@property
164+
def perimeter(self):
165+
return 2 * math.pi * self.radius
166+
167+
在这里,我们通过使用properties,将所有的访问接口形式统一起来,
168+
对半径、直径、周长和面积的访问都是通过属性访问,就跟访问简单的attribute是一样的。
169+
如果不这样做的话,那么就要在代码中混合使用简单属性访问和方法调用。
170+
下面是使用的实例:
171+
172+
.. code-block:: python
173+
174+
>>> c = Circle(4.0)
175+
>>> c.radius
176+
4.0
177+
>>> c.area # Notice lack of ()
178+
50.26548245743669
179+
>>> c.perimeter # Notice lack of ()
180+
25.132741228718345
181+
>>>
182+
183+
尽管properties可以实现优雅的编程接口,但有些时候你还是会想直接使用getter和setter函数。例如:
184+
185+
.. code-block:: python
186+
187+
>>> p = Person('Guido')
188+
>>> p.get_first_name()
189+
'Guido'
190+
>>> p.set_first_name('Larry')
191+
>>>
192+
193+
这种情况的出现通常是因为Python代码被集成到一个大型基础平台架构或程序中。
194+
例如,有可能是一个Python类准备加入到一个基于远程过程调用的大型分布式系统中。
195+
这种情况下,直接使用get/set方法(普通方法调用)而不是property或许会更容易兼容。
196+
197+
最后一点,不要像下面这样写有大量重复代码的property定义:
198+
199+
.. code-block:: python
200+
201+
class Person:
202+
def __init__(self, first_name, last_name):
203+
self.first_name = first_name
204+
self.last_name = last_name
205+
206+
@property
207+
def first_name(self):
208+
return self._first_name
209+
210+
@first_name.setter
211+
def first_name(self, value):
212+
if not isinstance(value, str):
213+
raise TypeError('Expected a string')
214+
self._first_name = value
215+
216+
# Repeated property code, but for a different name (bad!)
217+
@property
218+
def last_name(self):
219+
return self._last_name
220+
221+
@last_name.setter
222+
def last_name(self, value):
223+
if not isinstance(value, str):
224+
raise TypeError('Expected a string')
225+
self._last_name = value
226+
227+
重复代码会导致臃肿、易出错和丑陋的程序。好消息是,通过使用装饰器或闭包,有很多种更好的方法来完成同样的事情。
228+
可以参考8.9和9.21小节的内容。

0 commit comments

Comments
 (0)