Skip to content

Commit ed3c43f

Browse files
committed
8.8小节完成~
1 parent 3349333 commit ed3c43f

File tree

2 files changed

+295
-4
lines changed

2 files changed

+295
-4
lines changed

cookbook/c08/p08_extend_property.py

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
#!/usr/bin/env python
2+
# -*- encoding: utf-8 -*-
3+
"""
4+
Topic: 扩展property的功能
5+
Desc :
6+
"""
7+
8+
9+
class Person:
10+
def __init__(self, name):
11+
self.name = name
12+
13+
# Getter function
14+
@property
15+
def name(self):
16+
return self._name
17+
18+
# Setter function
19+
@name.setter
20+
def name(self, value):
21+
if not isinstance(value, str):
22+
raise TypeError('Expected a string')
23+
self._name = value
24+
25+
# Deleter function
26+
@name.deleter
27+
def name(self):
28+
raise AttributeError("Can't delete attribute")
29+
30+
31+
class SubPerson(Person):
32+
@property
33+
def name(self):
34+
print('Getting name')
35+
return super().name
36+
37+
@name.setter
38+
def name(self, value):
39+
print('Setting name to', value)
40+
super(SubPerson, SubPerson).name.__set__(self, value)
41+
42+
@name.deleter
43+
def name(self):
44+
print('Deleting name')
45+
super(SubPerson, SubPerson).name.__delete__(self)
46+
47+
48+
s = SubPerson('Guido')
49+
print(s.name)
50+
s.name = 'Larry'
51+
52+
53+
# A descriptor
54+
class String:
55+
def __init__(self, name):
56+
self.name = name
57+
58+
def __get__(self, instance, cls):
59+
if instance is None:
60+
return self
61+
return instance.__dict__[self.name]
62+
63+
def __set__(self, instance, value):
64+
if not isinstance(value, str):
65+
raise TypeError('Expected a string')
66+
instance.__dict__[self.name] = value
67+
68+
69+
# A class with a descriptor
70+
class Person:
71+
name = String('name')
72+
73+
def __init__(self, name):
74+
self.name = name
75+
76+
77+
# Extending a descriptor with a property
78+
class SubPerson(Person):
79+
@property
80+
def name(self):
81+
print('Getting name')
82+
return super().name
83+
84+
@name.setter
85+
def name(self, value):
86+
print('Setting name to', value)
87+
super(SubPerson, SubPerson).name.__set__(self, value)
88+
89+
@name.deleter
90+
def name(self):
91+
print('Deleting name')
92+
super(SubPerson, SubPerson).name.__delete__(self)
Lines changed: 203 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,217 @@
11
============================
2-
8.8 子类中扩展属性
2+
8.8 子类中扩展property
33
============================
44

55
----------
66
问题
77
----------
8-
todo...
8+
在子类中,你想要扩展定义在父类中的property的功能。
9+
10+
|
911
1012
----------
1113
解决方案
1214
----------
13-
todo...
15+
考虑如下的代码,它定义了一个property:
16+
17+
.. code-block:: python
18+
19+
class Person:
20+
def __init__(self, name):
21+
self.name = name
22+
23+
# Getter function
24+
@property
25+
def name(self):
26+
return self._name
27+
28+
# Setter function
29+
@name.setter
30+
def name(self, value):
31+
if not isinstance(value, str):
32+
raise TypeError('Expected a string')
33+
self._name = value
34+
35+
# Deleter function
36+
@name.deleter
37+
def name(self):
38+
raise AttributeError("Can't delete attribute")
39+
40+
下面是一个示例类,它继承自Person并扩展了 ``name`` 属性的功能:
41+
42+
.. code-block:: python
43+
44+
class SubPerson(Person):
45+
@property
46+
def name(self):
47+
print('Getting name')
48+
return super().name
49+
50+
@name.setter
51+
def name(self, value):
52+
print('Setting name to', value)
53+
super(SubPerson, SubPerson).name.__set__(self, value)
54+
55+
@name.deleter
56+
def name(self):
57+
print('Deleting name')
58+
super(SubPerson, SubPerson).name.__delete__(self)
59+
60+
接下来使用这个新类:
61+
62+
.. code-block:: python
63+
64+
>>> s = SubPerson('Guido')
65+
Setting name to Guido
66+
>>> s.name
67+
Getting name
68+
'Guido'
69+
>>> s.name = 'Larry'
70+
Setting name to Larry
71+
>>> s.name = 42
72+
Traceback (most recent call last):
73+
File "<stdin>", line 1, in <module>
74+
File "example.py", line 16, in name
75+
raise TypeError('Expected a string')
76+
TypeError: Expected a string
77+
>>>
78+
79+
如果你仅仅只想扩展property的某一个方法,那么可以像下面这样写:
80+
81+
.. code-block:: python
82+
83+
class SubPerson(Person):
84+
@Person.name.getter
85+
def name(self):
86+
print('Getting name')
87+
return super().name
88+
89+
或者,你只想修改setter方法,就这么写:
90+
91+
.. code-block:: python
92+
93+
class SubPerson(Person):
94+
@Person.name.setter
95+
def name(self, value):
96+
print('Setting name to', value)
97+
super(SubPerson, SubPerson).name.__set__(self, value)
98+
99+
|
14100
15101
----------
16102
讨论
17103
----------
18-
todo...
104+
在子类中扩展一个property可能会引起很多不易察觉的问题,
105+
因为一个property其实是 ``getter``、``setter`` 和 ``deleter`` 方法的集合,而不是单个方法。
106+
因此,但你扩展一个property的时候,你需要先确定你是否要重新定义所有的方法还是说只修改其中某一个。
107+
108+
在第一个例子中,所有的property方法都被重新定义。
109+
在每一个方法中,使用了 ``super()`` 来调用父类的实现。
110+
在 ``setter`` 函数中使用 ``super(SubPerson, SubPerson).name.__set__(self, value)`` 的语句是没有错的。
111+
为了委托给之前定义的setter方法,需要将控制权传递给之前定义的name属性的 ``__set__()`` 方法。
112+
不过,获取这个方法的唯一途径是使用类变量而不是实例变量来访问它。
113+
这也是为什么我们要使用 ``super(SubPerson, SubPerson)`` 的原因。
114+
115+
如果你只想重定义其中一个方法,那只使用 @property 本身是不够的。比如,下面的代码就无法工作:
116+
117+
.. code-block:: python
118+
119+
class SubPerson(Person):
120+
@property # Doesn't work
121+
def name(self):
122+
print('Getting name')
123+
return super().name
124+
125+
如果你试着运行会发现setter函数整个消失了:
126+
127+
.. code-block:: python
128+
129+
>>> s = SubPerson('Guido')
130+
Traceback (most recent call last):
131+
File "<stdin>", line 1, in <module>
132+
File "example.py", line 5, in __init__
133+
self.name = name
134+
AttributeError: can't set attribute
135+
>>>
136+
137+
你应该像之前说过的那样修改代码:
138+
139+
.. code-block:: python
140+
141+
class SubPerson(Person):
142+
@Person.getter
143+
def name(self):
144+
print('Getting name')
145+
return super().name
146+
147+
这么写后,property之前已经定义过的方法会被复制过来,而getter函数被替换。然后它就能按照期望的工作了:
148+
149+
.. code-block:: python
150+
151+
>>> s = SubPerson('Guido')
152+
>>> s.name
153+
Getting name
154+
'Guido'
155+
>>> s.name = 'Larry'
156+
>>> s.name
157+
Getting name
158+
'Larry'
159+
>>> s.name = 42
160+
Traceback (most recent call last):
161+
File "<stdin>", line 1, in <module>
162+
File "example.py", line 16, in name
163+
raise TypeError('Expected a string')
164+
TypeError: Expected a string
165+
>>>
166+
167+
在这个特别的解决方案中,我们没办法使用更加通用的方式去替换硬编码的 ``Person`` 类名。
168+
如果你不知道到底是哪个基类定义了property,
169+
那你只能通过重新定义所有property并使用 ``super()`` 来将控制权传递给前面的实现。
170+
171+
值的注意的是上面演示的第一种技术还可以被用来扩展一个描述器(在8.9小节我们有专门的介绍)。比如:
172+
173+
.. code-block:: python
174+
175+
# A descriptor
176+
class String:
177+
def __init__(self, name):
178+
self.name = name
179+
180+
def __get__(self, instance, cls):
181+
if instance is None:
182+
return self
183+
return instance.__dict__[self.name]
184+
185+
def __set__(self, instance, value):
186+
if not isinstance(value, str):
187+
raise TypeError('Expected a string')
188+
instance.__dict__[self.name] = value
189+
190+
# A class with a descriptor
191+
class Person:
192+
name = String('name')
193+
194+
def __init__(self, name):
195+
self.name = name
196+
197+
# Extending a descriptor with a property
198+
class SubPerson(Person):
199+
@property
200+
def name(self):
201+
print('Getting name')
202+
return super().name
203+
204+
@name.setter
205+
def name(self, value):
206+
print('Setting name to', value)
207+
super(SubPerson, SubPerson).name.__set__(self, value)
208+
209+
@name.deleter
210+
def name(self):
211+
print('Deleting name')
212+
super(SubPerson, SubPerson).name.__delete__(self)
213+
214+
最后值的注意的是,读到这里时,你应该会发现子类化 ``setter`` 和 ``deleter`` 方法其实是很简单的。
215+
这里演示的解决方案同样适用,但是在 `Python的issue页面 <http://bugs.python.org/issue14965>`_
216+
报告的一个bug,或许会使得将来的Python版本中出现一个更加简洁的方法。
217+

0 commit comments

Comments
 (0)