内容目录
1.介绍
这份指南是几个月内最有价值的Blog投稿精华。它的主题是向大家讲述Python中的神奇方法。__new__(cls,[...)
一个对象的实例化时__new__是第一个被调用的方法。在类中传递其他任何参数到__init__。__new__很少被使用,这样做确实有其目的,特别是当一个子类继承一个不可改变的类型(一个元组或一个字符串)时。我不打算再继续深入追求__new__的细节了,因为这不会产生多大用处,因为在Python Docs内已经涵盖了一份巨详细的说明了。
把上述这些内容合在一起,就成了一份__init__和__del__的实际使用用例:
1
2
3
4
5
6
7
8
9
10
11
|
from os.path import join
class FileObject:
'''对文件对象的包装,确保文件在关闭时得到删除'''
def __init__(self, filepath='~', filename='sample.txt'):
# 按filepath,读写模式打开名为filename的文件
self.file=open(join(filepath,filename), 'r+')
def __del__(self):
self.file.close()
del self.file
|
3.使操作符在自定义类内工作
使用Python神奇方法的优势之一就是它提供了一种简单的方式能让对象的行为像内建类型。这意味着你可以避免用丑陋,反直觉和非标准方法执行基本运算。在某些语言中,通常会这样做:
1
2
|
if instance.equals(other_instance):
# do something
|
你也应该在Python确实会这样做,但同时它会增加用户的疑惑以及不必要的冗长。不同的库可能会对相同的运算采用不同的命名,这使得用户比平常干了更多的事。依靠神奇方法的力量,你可以定义一个方法(比如__eq__),然后带代替我们真实的意图:
1
2
|
if instance == other_instance:
# do something
|
现在你看到的是神奇方法力量的一部分。绝大多数都允许我们定义为运算符本身的意义,当用在我们自己定义的类上就像它们是内建类型。
举一个例子,设想对单词进行类定义。我们可能希望能够按内部对string的默认比较行为,即字典序(通过字母)来比较单词,也希望能够基于某些其他的准则,像是长度或音节数。在本例中,我们通过单词长度排序,以下给出实现:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
class Word(str):
'''单词类,比较定义是基于单词长度的'''
def __new__(cls, word):
# 注意,我们使用了__new__,这是因为str是一个不可变类型,
# 所以我们必须更早地初始化它(在创建时)
if ' ' in word:
print "单词内含有空格,截断到第一部分"
word = word[:word.index(' ')] # 在出现第一个空格之前全是字符了现在
return str.__new__(cls, word)
def __gt__(self, other):
return len(self) > len(other)
def __lt__(self, other):
return len(self) < len(other)
def __ge__(self, other):
return len(self) >= len(other)
def __le__(self, other):
return len(self) <= len(other)
|
some_object + other这是“常规的”加法。而反射其实相当于一回事,除了操作数改变了改变下位置:
other + some_object因此,所有这些神奇的方法会做同样的事等价于常规算术操作符,除了改变操作数的位置关系,比如第一个操作数和自身作为第二个。此外没有其他的操作方式。在大多数情况下,反射算术操作的结果等价于常规算术操作,所以你尽可以在刚重载完__radd__就调用__add__。干脆痛快:
Python也有各种各样的神奇方法允许用户自定义增量赋值行为。你可能已经熟悉增量赋值,它结合了“常规的”操作符和赋值。如果你仍不明白我在说什么,下面有一个例子:
1
2
|
x = 5
x += 1 # 等价 x = x + 1
|
这些方法都不会有返回值,因为赋值在Python中不会有任何返回值。反而它们只是改变类的状态。列表如下:
4.描述你的类
用一个字符串来说明一个类这通常是有用的。在Python中提供了一些方法让你可以在你自己的类中自定义内建函数返回你的类行为的描述。
__str__(self)5.属性访问控制
有许多从其他语言阵营转到Python来的人抱怨Python对类缺乏真正的封装(比如,没有办法自定义private属性,已经给出public的getter和setter)。这可不是真相哟:Python通过神奇的方法实现了大量的封装,而不是通过明确的方法或字段修饰符。请看:
__getattr__(self, name)你可以很容易地在你自定义任何类属性访问方法时引发一个问题。参考这个例子:
1
2
3
4
5
6
7
8
9
|
def __setattr__(self, name, value):
self.name = value
# 当每次给一个类属性赋值时,会调用__setattr__(),这就形成了递归
# 因为它真正的含义是 self.__setattr__('name', value)
# 所以这方法不停地调用它自己,变成了一个无法退出的递归最终引发crash
def __setattr__(self, name, value):
self.__dict__[name] = value # 给字典中的name赋值
# 在此自定义行为
|
再一次,Python的神奇方法向我们展示了其难以置信的能力,同时巨大的力量也伴随着重大的责任。重要的是让你明白正确使用神奇方法,这样你就不会破坏其他代码。
那么,我们在关于定制类属性访问中学习了什么?不要轻易地使用,事实上它过于强大以及反直觉。这也是它为何存在的理由:Python寻求干坏事的可能性,但会把它们弄得很难。自由是至高无上的,所以你可以做任何你想做的事情。以下是一个关于特殊属性访问方法的实际例子(注意,我们使用super因为并非所有类都有__dict__类属性):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
class AccessCounter:
'''一个类包含一个值和实现了一个访问计数器。
当值每次发生变化时,计数器+1'''
def __init__(self, val):
super(AccessCounter, self).__setattr__('counter',0)
super(AccessCounter, self).__setattr__('value', val)
def __setattr__(self, name, value):
if name == 'value':
super(AccessCounter, self).__setattr__('counter', self.counter + 1)
# Make this unconditional.
# 如果你想阻止其他属性被创建,抛出AttributeError(name)异常
super(AccessCounter, self).__setattr__(name, value)
def __delattr__(self, name)
if name == 'value':
super(AccessCounter, self).__setattr__('counter', self.counter + 1)
super(AccessCounter, self).__delattr__(name)
|
6.制作自定义序列
很有多种方式可以让你的类表现得像内建序列(字典,元组,列表,字符串等)。这些是我迄今为止最喜欢的神奇方法了,因为不合理的控制它们赋予了你一种魔术般地让你的类实例整个全局函数数组漂亮工作的方式。在我们开始讲解这个内容之前,让我们先快速理清需求。
在我们的例子中,让我们看一下一个list实现的某些基础功能性的构建。可能会让你想起你使用的其他语言(比如Haskell)。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
|
class FunctionalList:
'''类覆盖了一个list的某些额外的功能性魔法,像head,
tail,init,last,drop,and take'''
def __init__(self, values=None):
if values is None:
self.values = []
else:
self.values = values
def __len__(self):
return len(self.values)
def __getitem__(self, key):
# 如果key是非法的类型和值,那么list valuse会抛出异常
return self.values[key]
def __setitem__(self, key, value):
self.values[key] = value
def __delitem__(self, key):
del self.values[key]
def __iter__(self):
return iter(self.values)
def __reversed__(self):
return reversed(self.values)
def append(self, value):
self.values.append(value)
def head(self):
# 获得第一个元素
return self.values[0]
def tail(self):
# 获得在第一个元素后的其他所有元素
return self.values[1:]
def init(self):
# 获得除最后一个元素的序列
return self.values[:-1]
def last(last):
# 获得最后一个元素
return self.values[-1]
def drop(self, n):
# 获得除前n个元素的序列
return self.values[n:]
def take(self, n):
# 获得前n个元素
return self.values[:n]
|
7.反射
你也可以通过定义神奇方法来控制如何反射使用内建函数isinstance()和issubclass()的行为。这些神奇方法是:
__instancecheck__(self, instance)8.可调用对象
正如你可能已经知道,在Python中函数是第一类对象。这就意味着它们可以被传递到函数和方法,就像是任何类型的对象。这真是一种难以置信强大的特性。
这是Python中一个特别的神奇方法,它允许你的类实例像函数。所以你可以“调用”它们,把他们当做参数传递给函数等等。这是另一个强大又便利的特性让Python的编程变得更可爱了。__call__可能对于那些经常改变状态的实例来说是极其有用的。“调用”实例是一种顺应直觉且优雅的方式来改变对象的状态。下面一个例子是一个类表示一个实体在一个平面上的位置:
1
2
3
4
5
6
7
8
9
10
11
12
|
class Entity:
'''描述实体的类,被调用的时候更新实体的位置'''
def __init__(self, size, x, y):
self.x, self.y = x, y
self.size = size
def __call__(self, x, y):
'''改变实体的位置'''
self.x, self.y = x, y
#省略...
|
9.上下文管理
在Python2.5里引入了一个新关键字(with)使得一个新方法得到了代码复用。上下文管理这个概念在Python中早已不是新鲜事了(之前它作为库的一部分被实现过),但直到PEP343(http://www.python.org/dev/peps/pep-0343/)才作为第一个类语言结构取得了重要地位而被接受。你有可能早就已经见识过with声明:
1
2
|
with open('foo.txt') as bar:
# 对bar执行某些动作
|
上下文管理允许对对象进行设置和清理动作,用with声明进行已经封装的操作。上下文操作的行为取决于2个神奇方法:
__enter__和__exit__对那些已有良好定义和对设置,清理行为有共同行为的特殊类是有用。你也可以使用这些方法去创建封装其他对象通用的上下文管理。看下面的例子:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
class Closer:
'''用with声明一个上下文管理用一个close方法自动关闭一个对象'''
def __init__(self, obj):
self.obj = obj
def __enter__(self):
return self.obj # 绑定目标
def __exit__(self, exception_type, exception_val, trace):
try:
self.obj.close()
except AttributeError: #obj不具备close
print 'Not closable.'
return True # 成功处理异常
|
以下是一个对于Closer实际应用的一个例子,使用一个FTP连接进行的演示(一个可关闭的套接字):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
>>> from magicmethods import Closer
>>> from ftplib import :;;
>>> with Closer(FTP('ftp.somsite.com')) as conn:
... conn.dir()
...
# 省略的输出
>>> conn.dir()
# 一个很长的AttributeError消息, 不能关闭使用的一个连接
>>> with Closer(int(5)) as i:
... i += 1
...
Not closeable.
>>> i
6
|
10.构建描述符对象
描述符可以改变其他对象,也可以是访问类中任一的getting,setting,deleting。描述符不意味着孤立;相反,它们意味着会被它们的所有者类控制。当建立面向对象数据库或那些拥有相互依赖的属性的类时,描述符是有用的。当描述符在几个不同单元或描述计算属性时显得更为有用。
作为一个描述符,一个类必须至少实现__get__,__set__,和__delete__中的一个。让我们快点看一下这些神奇方法吧:现在,有一个有用的描述符应用例子:单位转换策略
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
class Meter(object):
'''米描述符'''
def __init__(self, value=0.0):
self.value = float(value)
def __get__(self, instance, owner):
return self.value
def __set__(self, instance, value):
self.value = float(value)
class Foot(object):
'''英尺描述符'''
def __get__(self, instance, owner):
return instance.meter * 3.2808
def __set__(self, instance, value):
instance.meter = float(value) / 3.2808
class Distance(object):
'''表示距离的类,控制2个描述符:feet和meters'''
meter = Meter()
foot = Foot()
|
11.Pickling你的对象
假如你花时间和其他Pythonistas打交道,那么你至少有可能听到过Pickling这个词。Pickling是一种对Python数据结构的序列化过程。如果你需要存储一个对象,之后再取回它(通常是为了缓存)那么它就显得格外地有用了。同时,它也是产生忧虑和困惑的主要来源。
Pickling是那么地重要以至于它不仅有自己专属的模块(pickle),还有自己的protocol和神奇方法与其相伴。但首先用简要的文字来解释下如何pickle已经存在的类型(如果你已经懂了可以随意跳过这部分内容)让我们跳入pickling。话说你有一个词典你想要保存它并在稍后取回。你可以把它的内容写到一个文件中去,需要非常小心地确保你写了正确的语法,然后用exec()或处理文件的输入取回写入的内容。但这是不稳定的:如果你你在纯文本中保存重要的数据,它有可能被几种方法改变,导致你的程序crash或在你的计算机上运行了恶意代码而出错。于是,我们准备pickle它:
1
2
3
4
5
6
7
8
|
import pickle
data = {'foo': [1,2,3],
'bar': ('Hello','world!'),
'baz': True}
jar = open('data.pk1', 'wb')
pickle.dump(data, jar) # 把pickled数据写入jar文件
jar.close()
|
好了现在,已经过去了几个小时。我们希望拿回数据,而我们需要做的事仅仅是unpickle它:
1
2
3
4
5
6
|
import pickle
pk1_file = open('data.pk1','rb') #连接pickled数据
data = pickle.load(pk1_file) #把数据load到一个变量中去
print data
pk1_file.close()
|
发生了什么事?正如你的预期,我们获得了data。
我们的例子是Slate类,它会记忆它曾经的值和已经写入的值。然而,当这特殊的slate每一次pickle都会被清空:当前值不会被保存。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
|
import time
class Slate:
'''存储一个字符串和一个变更log,当Pickle时会忘记它的值'''
def __init__(self, value):
self.value = value
self.last_change = time.asctime()
self.history = {}
def change(self, new_value):
# 改变值,提交最后的值到历史记录
self.history[self.last_change] = self.value
self.value = new_value
self.last_change = time.asctime()
def print_changes(self):
print 'Changelog for Slate object:'
for k, v in self.history.items():
print '%st %s' % (k, v)
def __getstate__(self):
# 故意不返回self.value 或 self.last_change.
# 当unpickle,我们希望有一块空白的"slate"
return self.history
def __setstate__(self, state):
# 让 self.history = state 和 last_change 和 value被定义
self.history = state
self.value, self.last_change = None, None
|
12.总结
这份指南的目标就是任何人读一读它,不管读者们是否具备Python或面对对象的编程经验。如果你正准备学习Python,那你已经获得了编写功能丰富,优雅,易用的类的宝贵知识。如果你是一名中级Python程序员,你有可能已经拾起了一些新概念和策略和一些好的方法来减少你和你的用户编写的代码量。如果你是一名Pythonista专家,你可能已经回顾了某些你可能已经被你遗忘的知识点,或着你又学习到了一些新技巧。不管你的的经验等级,我希望这次Python神奇方法的旅程达到了真正神奇的效果。(我无法控制自己在最后不用个双关语)
附录:如果调用神奇方法
Python中的一些神奇方法直接映射到内建函数;在这种情况下,调用它们的方法是相当明显的。然而,在其他情况下,那些调用方法就不这么明显了。本附录致力于揭开能够引导神奇方法被调用的非明显语法。
神奇方法 | 调用方法 | 说明 |
__new__(cls [,...]) |
instance = MyClass(arg1, arg2) |
__new__ 在创建实例的时候被调用 |
__init__(self [,...]) |
instance = MyClass(arg1, arg2) |
__init__ 在创建实例的时候被调用 |
__cmp__(self, other) |
self == other, self > other, 等 |
在比较的时候调用 |
__pos__(self) |
+self |
一元加运算符 |
__neg__(self) |
-self |
一元减运算符 |
__invert__(self) |
~self |
取反运算符 |
__index__(self) |
x[self] |
对象被作为索引使用的时候 |
__nonzero__(self) |
bool(self) |
对象的布尔值 |
__getattr__(self, name) |
self.name # name不存在 |
访问一个不存在的属性时 |
__setattr__(self, name, val) |
self.name = val |
对一个属性赋值时 |
__delattr__(self, name) |
del self.name |
删除一个属性时 |
__getattribute(self, name) |
self.name |
访问任何属性时 |
__getitem__(self, key) |
self[key] |
使用索引访问元素时 |
__setitem__(self, key, val) |
self[key] = val |
对某个索引值赋值时 |
__delitem__(self, key) |
del self[key] |
删除某个索引值时 |
__iter__(self) |
for x in self |
迭代时 |
__contains__(self, value) |
value in self, value not in self |
使用 in 操作测试关系时 |
__concat__(self, value) |
self + other |
连接两个对象时 |
__call__(self [,...]) |
self(args) |
“调用”对象时 |
__enter__(self) |
with self as x: |
with语句上下文管理 |
__exit__(self, exc, val, trace) |
with self as x: |
with语句上下文管理 |
__getstate__(self) |
pickle.dump(pkl_file, self) |
序列化 |
__setstate__(self) |
data = pickle.load(pkl_file) |
序列化 |
转自:http://article.yeeyan.org/view/311527/287706