Skip to content

Commit 17ceeed

Browse files
committed
p3
1 parent 90e7df0 commit 17ceeed

File tree

4 files changed

+312
-5
lines changed

4 files changed

+312
-5
lines changed

240.md

Lines changed: 282 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,282 @@
1+
>我们既蒙怜悯,受了这职分,就不丧胆,乃将那些暗昧可耻的事弃绝了,不行诡诈,不谬讲神的道理,只将真理表明出来,好在神面前把自己荐与各人的良心。(2 CORINTHIANS 4:1-2)
2+
3+
#黑魔法
4+
5+
围绕类的话题,真实说也说不完,仅特殊方法,除了前面遇到过的`__init__()``__new__()``__str__()`等之外,还有很多。虽然它们仅仅是在某些特殊情景中使用,但是,因为本教程是“From Beginner to Master”。当然,不是学习了类的更多特殊方法就能达到Master水平,但是这是通往Master的一步。
6+
7+
本节试图再介绍一些点“黑魔法”,既能窥探到Python的更高境界,也能感受到Master的未来能力。俗话说“艺不压身”,还是多学点好。
8+
9+
##优化内存的`__slots__`
10+
11+
首先声明,`__slots__`能够限制属性的定义,但是这不是它存在终极目标,它存在的终极目标更应该是一个在编程中非常重要的方面:优化内存使用。
12+
13+
>>> class Spring(object):
14+
... __slots__ = ("tree", "flower")
15+
...
16+
>>> dir(Spring)
17+
['__class__', '__delattr__', '__doc__', '__format__', '__getattribute__', '__hash__', '__init__', '__module__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__slots__', '__str__', '__subclasshook__', 'flower', 'tree']
18+
19+
仔细看看`dir()`的结果,还有`__dict__`属性吗?没有了,的确没有了。也就是说`__slots__``__dict__`挤出去了,它进入了类的属性。
20+
21+
>>> Spring.__slots__
22+
('tree', 'flower')
23+
24+
这里可以看出,类Spring有且仅有两个属性。
25+
26+
>>> t = Spring()
27+
>>> t.__slots__
28+
('tree', 'flower')
29+
30+
实例化之后,实例的`__slots__`与类的完全一样,这跟前面的`__dict__`大不一样了。
31+
32+
>>> Spring.tree = "liushu"
33+
34+
通过类,先赋予一个属性值。然后,检验一下实例能否修改这个属性:
35+
36+
>>> t.tree = "guangyulan"
37+
Traceback (most recent call last):
38+
File "<stdin>", line 1, in <module>
39+
AttributeError: 'Spring' object attribute 'tree' is read-only
40+
41+
看来,我们的意图不能达成,报错信息中显示,`tree`这个属性是只读的,不能修改了。
42+
43+
>>> t.tree
44+
'liushu'
45+
46+
因为前面已经通过类给这个属性赋值了。不能用实例属性来修改。只能:
47+
48+
>>> Spring.tree = "guangyulan"
49+
>>> t.tree
50+
'guangyulan'
51+
52+
用类属性修改。但是对于没有用类属性赋值的,可以通过实例属性赋值。
53+
54+
>>> t.flower = "haitanghua"
55+
>>> t.flower
56+
'haitanghua'
57+
58+
但此时:
59+
60+
>>> Spring.flower
61+
<member 'flower' of 'Spring' objects>
62+
63+
实例属性的值并没有传回到类属性,你也可以理解为新建立了一个同名的实例属性。如果再给类属性赋值,那么就会这样了:
64+
65+
>>> Spring.flower = "ziteng"
66+
>>> t.flower
67+
'ziteng'
68+
69+
当然,此时在给`t.flower`重新赋值,就会爆出跟前面一样的错误了。
70+
71+
>>> t.water = "green"
72+
Traceback (most recent call last):
73+
File "<stdin>", line 1, in <module>
74+
AttributeError: 'Spring' object has no attribute 'water'
75+
76+
这里试图给实例新增一个属性,也失败了。
77+
78+
看来`__slots__`已经把实例属性牢牢地管控了起来,但更本质是的是优化了内存。诚然,这种优化会在大量的实例时候显出效果。
79+
80+
书接上回,不管是实例还是类,都用`__dict__`来存储属性和方法,可以笼统地把属性和方法称为成员或者特性,一句话概括,就是`__dict__`存储对象成员。但,有时候访问的对象成员没有存在其中,就是这样:
81+
82+
>>> class A(object):
83+
... pass
84+
...
85+
>>> a = A()
86+
>>> a.x
87+
Traceback (most recent call last):
88+
File "<stdin>", line 1, in <module>
89+
AttributeError: 'A' object has no attribute 'x'
90+
91+
`x`不是实例的成员,用`a.x`访问,就出错了,并且错误提示中报告了原因:“'A' object has no attribute 'x'”
92+
93+
在很多情况下,这种报错是足够的了。但是,在某种我现在还说不出的情况下,你或许不希望这样报错,或许希望能够有某种别的提示、操作等。也就是我们更希望能在成员不存在的时候有所作为,不是等着报错。
94+
95+
要处理类似的问题,就要用到本节中的知识了。
96+
97+
##属性拦截
98+
99+
有时候,访问某个类或者实例属性,它不存在,就会异常。对于异常,总是要处理的。就好像“寻隐者不遇”,却被童子“遥指杏花村”,将你“拦截”了,不至于因为“不遇”而垂头丧气。
100+
101+
在Python中,有一些方法就具有这种“拦截”能力。
102+
103+
- `__setattr__(self, name,value)`:如果要给name赋值,就调用这个方法。
104+
- `__getattr__(self, name)`:如果name被访问,同时它不存在的时候,此方法被调用。
105+
- `__getattribute__(self, name)`:当name被访问时自动被调用(注意:这个仅能用于新式类),无论name是否存在,都要被调用。
106+
- `__delattr__(self, name)`:如果要删除name,这个方法就被调用。
107+
108+
用例子说明。
109+
110+
>>> class A(object): #Python 3: class A:
111+
... def __getattr__(self, name):
112+
... print "You use getattr" #Python 3: print("You use getattr"),下同,从略
113+
... def __setattr__(self, name, value):
114+
... print "You use setattr"
115+
... self.__dict__[name] = value
116+
...
117+
118+
`A`除了两个方法,没有别的了。
119+
120+
>>> a = A()
121+
>>> a.x
122+
You use getattr
123+
124+
`a.x`这个实例属性,本来是不存在的,但是,由于类中有了`__getattr__(self, name)`方法,当发现属性`x`不存在于对象的`__dict__`中的时候,就调用了`__getattr__`,即所谓“拦截成员”。
125+
126+
>>> a.x = 7
127+
You use setattr
128+
129+
给对象的属性赋值时候,调用了`__setattr__(self, name, value)`方法,这个方法中有一句`self.__dict__[name] = value`,通过这个语句,就将属性和数据保存到了对象的`__dict__`中,如果再调用这个属性:
130+
131+
>>> a.x
132+
7
133+
134+
它已经存在于对象的`__dict__`之中。
135+
136+
在上面的类中,当然可以使用`__getattribute__(self, name)`,并且,只要访问属性就会调用它。例如:
137+
138+
>>> class B(object):
139+
... def __getattribute__(self, name):
140+
... print "you are useing getattribute"
141+
... return object.__getattribute__(self, name)
142+
143+
为了与前面的类区分,新命名一个类名字。需要提醒注意,在这里返回的内容用的是`return object.__getattribute__(self, name)`,而没有使用`return self.__dict__[name]`样式。因为如果用`return self.__dict__[name]`这样的方式,就是访问`self.__dict__`,只要访问这个属性,就要调用`__getattribute__``,这样就导致了无线递归下去(死循环)。要避免之。
144+
145+
>>> b = B()
146+
>>> b.y
147+
you are useing getattribute
148+
Traceback (most recent call last):
149+
File "<stdin>", line 1, in <module>
150+
File "<stdin>", line 4, in __getattribute__
151+
AttributeError: 'B' object has no attribute 'y'
152+
>>> b.two
153+
you are useing getattribute
154+
Traceback (most recent call last):
155+
File "<stdin>", line 1, in <module>
156+
File "<stdin>", line 4, in __getattribute__
157+
AttributeError: 'B' object has no attribute 'two'
158+
159+
访问不存在的成员,可以看到,已经被`__getattribute__`拦截了,虽然最后还是要报错的。
160+
161+
>>> b.y = 8
162+
>>> b.y
163+
you are useing getattribute
164+
8
165+
166+
当给其赋值后,意味着已经在`__dict__`里面了,再调用,依然被拦截,但是由于已经在`__dict__`内,会把结果返回。
167+
168+
当你看到这里,是不是觉得上面的方法有点魔力呢?不错,的确是“黑魔法”。但是,它有什么具体应用呢?看下面的例子,能给你带来启发。
169+
170+
#!/usr/bin/env python
171+
# coding=utf-8
172+
173+
"""
174+
study __getattr__ and __setattr__
175+
"""
176+
177+
class Rectangle(object): #Python 3: class Rectangle:
178+
"""
179+
the width and length of Rectangle
180+
"""
181+
def __init__(self):
182+
self.width = 0
183+
self.length = 0
184+
185+
def setSize(self, size):
186+
self.width, self.length = size
187+
def getSize(self):
188+
return self.width, self.length
189+
190+
if __name__ == "__main__":
191+
r = Rectangle()
192+
r.width = 3
193+
r.length = 4
194+
print r.getSize() #Python 3: print(r.getSize())
195+
r.setSize( (30, 40) )
196+
print r.width #Python 3: print(r.width)
197+
print r.length #Python 3: print(r.length)
198+
199+
上面代码来自《Beginning Python:From Novice to Professional,Second Edittion》(by Magnus Lie Hetland),根据本教程的需要,稍作修改。
200+
201+
$ python 21301.py
202+
(3, 4)
203+
30
204+
40
205+
206+
这段代码已经可以正确运行了。但是,作为一个精益求精的程序员。总觉得那种调用方式还有可以改进的空间。比如,要给长宽赋值的时候,必须赋予一个元组,里面包含长和宽。这个能不能改进一下呢?
207+
208+
#!/usr/bin/env python
209+
# coding=utf-8
210+
211+
"""
212+
study __getattr__ and __setattr__
213+
"""
214+
215+
class Rectangle(object): #Python 3: class Rectangle:
216+
"""
217+
the width and length of Rectangle
218+
"""
219+
def __init__(self):
220+
self.width = 0
221+
self.length = 0
222+
223+
def setSize(self, size):
224+
self.width, self.length = size
225+
def getSize(self):
226+
return self.width, self.length
227+
228+
size = property(getSize, setSize)
229+
230+
if __name__ == "__main__":
231+
r = Rectangle()
232+
r.width = 3
233+
r.length = 4
234+
print r.size
235+
r.size = 30, 40
236+
print r.width
237+
print r.length
238+
239+
以上代码的运行结果同上。但是,因为加了一句`size = property(getSize, setSize)`,使得调用方法是不是更优雅了呢?原来用`r.getSize()`,现在使用`r.size`,就好像调用一个属性一样。难道你不觉得眼熟吗?在[《多态和封装》](./211.md)中已经用到过property函数了,虽然写法略有差别,但是作用一样。
240+
241+
本来,这样就已经足够了。但是,因为本节中出来了特殊方法,所以,一定要用这些特殊方法从新演绎一下这段程序。虽然重新演绎的不一定比原来的好,主要目的是演示本节的特殊方法应用。
242+
243+
#!/usr/bin/env python
244+
# coding=utf-8
245+
246+
class NewRectangle(object):
247+
def __init__(self):
248+
self.width = 0
249+
self.length = 0
250+
251+
def __setattr__(self, name, value):
252+
if name == "size":
253+
self.width, self.length = value
254+
else:
255+
self.__dict__[name] = value
256+
257+
def __getattr__(self, name):
258+
if name == "size":
259+
return self.width, self.length
260+
else:
261+
raise AttributeError
262+
263+
if __name__ == "__main__":
264+
r = NewRectangle()
265+
r.width = 3
266+
r.length = 4
267+
print r.size #Python 3: print(r.size)
268+
r.size = 30, 40
269+
print r.width #Python 3: print(r.width)
270+
print r.length #Python 3: print(r.length)
271+
272+
除了类的样式变化之外,调用样式没有变。结果是一样的。
273+
274+
如果要对于这种黑魔法有更深的理解,可以阅读:[Python Attributes and Methods](http://www.cafepy.com/article/python_attributes_and_methods/python_attributes_and_methods.html),读了这篇文章,对Python的对象属性和方法会有更深入的理解。
275+
276+
至此,是否注意到,我们使用了很多以双下划线开头和结尾的方法或者属性,比如`__dict__``__init__()`等。在Python中,用这种方法表示特殊的方法和属性,当然,这是一个惯例,之所以这样做,主要是确保这些特殊的名字不会跟你自己所定义的名称冲突,我们自己定义名称的时候,是绝少用双划线开头和结尾的。如果你需要重写这些方法,当然是可以的。
277+
278+
------
279+
280+
[总目录](./index.md)&nbsp;&nbsp;&nbsp;|&nbsp;&nbsp;&nbsp;[上节:定制类](./239.md)&nbsp;&nbsp;&nbsp;|&nbsp;&nbsp;&nbsp;[下节:迭代器](./214.md)
281+
282+
如果你认为有必要打赏我,请通过支付宝:**[email protected]**,不胜感激。

2code/24001p3.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
#!/usr/bin/env python
2+
# coding=utf-8
3+
4+
class Rectangle(object):
5+
6+
def __init__(self):
7+
self.width = 0
8+
self.length = 0
9+
10+
def setSize(self, size):
11+
self.width, self.length = size
12+
def getSize(self):
13+
return self.width, self.length
14+
15+
if __name__ == "__main__":
16+
r = Rectangle()
17+
r.width = 3
18+
r.length = 4
19+
print(r.getSize())
20+
r.setSize( (30, 40) )
21+
print(r.width)
22+
print(r.length)

README.md

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@
22

33
>In the begning when God created the heavens and the earth. the earth was a formless void and darkness covered the face of the deep, while a wind from God swept over the face of the waters. Then God said,"Let there be light"; and there was light. And God saw that the light was good; and God separated the light from the darkness. (GENESIS 1:1-4)
44
5-
#《跟老齐学Python》(第二版):From beginner to master.
5+
#《跟老齐学Python》(第二版)
6+
7+
From beginner to master.
68

79
针对零基础的学习者,试图实现从基础到精通,还要看自己的造化。
810

@@ -75,8 +77,7 @@
7577
5. [类(5)](./209.md)==>继承,super,多重继承
7678
6. [多态和封装](./211.md)==>多态,封装和私有化
7779
7. [定制类](./239.md)==>类和类型,定制类
78-
8. [特殊方法(1)](./212.md)==>`__dict__``__slots__`
79-
9. [特殊方法(2)](./213.md)==>`__getattr__`,`__setattr__`以及查找属性顺序,双划线解释
80+
8. [黑魔法](./240.md)==>优化内存的`__slots__`,属性拦截
8081
10. [迭代器](./214.md)==>迭代器方法`__iter__`,`netx()`
8182
11. [生成器](./215.md)==>生成器定义,yield,生成器方法
8283
12. [上下文管理器](./235.md)==>上下文管理器的基本概念,使用方法和contextlib模块

index.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@
22

33
>In the begning when God created the heavens and the earth. the earth was a formless void and darkness covered the face of the deep, while a wind from God swept over the face of the waters. Then God said,"Let there be light"; and there was light. And God saw that the light was good; and God separated the light from the darkness. (GENESIS 1:1-4)
44
5-
#《跟老齐学Python》(第二版):From beginner to master.
5+
#《跟老齐学Python》(第二版)
6+
7+
From beginner to master.
68

79
针对零基础的学习者,试图实现从基础到精通,还要看自己的造化。
810

@@ -75,7 +77,7 @@
7577
5. [类(5)](./209.md)==>继承,super,多重继承
7678
6. [多态和封装](./211.md)==>多态,封装和私有化
7779
7. [定制类](./239.md)==>类和类型,定制类
78-
8. [特殊方法(1)](./212.md)==>`__dict__``__slots__`
80+
8. [黑魔法](./240.md)==>优化内存的`__slots__`,属性拦截
7981
9. [特殊方法(2)](./213.md)==>`__getattr__`,`__setattr__`以及查找属性顺序,双划线解释
8082
10. [迭代器](./214.md)==>迭代器方法`__iter__`,`netx()`
8183
11. [生成器](./215.md)==>生成器定义,yield,生成器方法

0 commit comments

Comments
 (0)