Python中编写类的各类技巧和方法

简介python

有关 Python 内编写类的各类技巧和方法(构建和初始化、重载操做符、类描述、属性访问控制、自定义序列、反射机制、可调用对象、上下文管理、构建描述符对象、Pickling)。 你能够把它看成一个教程,进阶,或者使用参考;我但愿它可以成为一份针对 Python 方法的用户友好指南。程序员

内容目录数据库

介绍
构建和初始化
使操做符在自定义类内工做
神奇方法——比较
神奇方法——数字
描述你的类
属性访问控制
制做自定义序列
反射
可调用对象
上下文管理
构建描述符对象
Pickling 你的对象

总结
附录:如何调用神奇方法

1.介绍编程

这份指南是几个月内最有价值的 Blog 投稿精华。它的主题是向你们讲述 Python 中的神奇方法。数组

何为神奇方法呢?它们是面向 Python 中的一切,是一些特殊的方法容许在本身的定义类中定义增长“神奇”的功能。它们老是使用双下划线(好比 __ init__ 或 __ lt__),但它们的文档没有很好地把它们表现出来。全部这些神奇方法都出如今Python的官方文档中,但内容相对分散,组织结构也显得松散。还有你会难以发现一个实例(虽然他们被设计很棒,在语言参考中被详细描述,可以后就会伴随着枯燥的语法描述等)。缓存

因此,为了解决我认为在 Python 文档中的一大败笔,我打算用更多纯英语,实例驱动的文档来讲明 Python 的神奇方法。而后我就开始花了几周的时间来写 blog,而如今我已经完成了它们,并将它们合订成一份指南。安全

我但愿你喜欢它。把它看成一个教程,进阶,或者使用参考;我但愿它可以成为一份针对 Python 方法的用户友好指南。数据结构

2.构建和初始化app

相信你们都熟悉这个最基础的神奇方法 __ init__。它令你能自定义一个对象的初始化行为。而当我调用x=SomeClass() 时, init 并非最早被调用的。实际上有一个叫作 new 的方法,事实上是它建立了实例,它传递任何参数给初始化程序来达到建立的目的。在对象生命周期结束时,调用 del。让咱们更近地观察下这 3 个神奇方法吧:socket

  • __ new__(cls,[...)

一个对象的实例化时 __ new__ 是第一个被调用的方法。在类中传递其余任何参数到 __ init__。__new__不多被使用,这样作确实有其目的,特别是当一个子类继承一个不可改变的类型(一个元组或一个字符串)时。我不打算再继续深刻追求 new 的细节了,由于这不会产生多大用处,由于在 Python Docs 内已经涵盖了一份巨详细的说明了。

  • __ init__(self,[...)

类的初始化。它会得到初始构建调用传过来的任何东西(举例来讲就是,当咱们调用x=SomeClass(10,'foo'),__ init__ 就会把传过来的 10 和 'foo' 做为参数。__init__在 Python 的类定义中几乎广泛被使用)

  • __ del__(self)

若是 __ new__和 init 是对象的构造器,那么 del 就是析构器。它不实现声明为del x(这样的代码不会解释成 x. del())的行为。相反,它定义为当一个对象被垃圾回收时的行为。这可能对可能须要额外清理的对象至关有用,好比 sockets 或文件对象。但要当心,若是对象仍处于存活状态而当被解释退出时, del 没有保证就会被执行,所以这样的 del 不能做为良好的编码规范的替代。(就像当你完成操做老是要关闭一次链接。但事实上, del 几乎永远不会执行,就由于它处于不安全状况被调用了。使用时保持警戒!)

把上述这些内容合在一块儿,就成了一份 __ init__ 和 __ del__ 的实际使用用例:

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 神奇方法的优点之一就是它提供了一种简单的方式能让对象的行为像内建类型。这意味着你能够避免用丑陋,反直觉和非标准方法执行基本运算。在某些语言中,一般会这样作:

if instance.equals(other_instance):
# do something

你也应该在 Python 确实会这样作,但同时它会增长用户的疑惑以及没必要要的冗长。不一样的库可能会对相同的运算采用不一样的命名,这使得用户比日常干了更多的事。依靠神奇方法的力量,你能够定义一个方法(好比 __ eq__),而后带代替咱们真实的意图:

if instance == other_instance:
# do something

如今你看到的是神奇方法力量的一部分。绝大多数都容许咱们定义为运算符自己的意义,当用在咱们本身定义的类上就像它们是内建类型。

3.1 神奇方法——比较

Python 有一整套神奇方法被设计用来经过操做符实现对象间直观的比较,而非别扭的方法调用。它们一样提供了一套覆盖 Python 对象比较的默认行为(经过引用)。如下是这些方法的列表以及作法:

__ cmp__(self, other)

__ cmp__是神奇方法中最基础的一个。实际上它实现全部比较操做符行为(<,==,!=,等),但它有可能不按你想要的方法工做(例如,一个实例是否等于另外一个这取决于比较的准则,以及一个实例是否大于其余的这也取决于其余的准则)。若是 self < other,那 cmp 应当返回一个负整数;若是 self == other,则返回 0;若是 self > other,则返回正整数。它一般是最好的定义,而不须要你一次就全定义好它们,但当你须要用相似的准则进行全部的比较时, cmp 会是一个很好的方式,帮你节省重复性和提升明确度。

__ eq__(self, other)

定义了相等操做符,==的行为。

__ ne__(self, other)

定义了不相等操做符,!= 的行为。

__ lt__(self, other)

定义了小于操做符,< 的行为。

__ gt__(self, other)

定义了大于操做符,> 的行为。

__ le__(self, other)

定义了小于等于操做符,<=的行为。

__ ge__(self, other)

定义了大于等于操做符,>= 的行为。

举一个例子,设想对单词进行类定义。咱们可能但愿可以按内部对 string 的默认比较行为,即字典序(经过字母)来比较单词,也但愿可以基于某些其余的准则,像是长度或音节数。在本例中,咱们经过单词长度排序,如下给出实现:

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)

如今,咱们能够建立 2 个单词(经过 Word('foo') 和 Word('bar'))并基于它们的长度进行比较了。注意,咱们没有定义 __ eq__ 和 __ ne__。这是由于这可能致使某些怪异的行为(特别是当比较 Word('foo') == Word('bar') 将会获得 True 的结果)。基于单词长度的相等比较会使人摸不清头脑,所以咱们就沿用了str 自己的相等比较的实现。

如今多是一个好时机来提醒你一下,你没必要重载每个比较相关的神奇方法来得到各类比较。标准库已经友好地为咱们在模板 functools 中提供了一个装饰(decorator)类,定义了全部比较方法。你能够只重载 __ eq__ 和一个其余的方法(好比 __ gt__, lt,等)。这个特性只在 Python2.7(后?)适用,但当你有机会的话应该尝试一下,它会为你省下大量的时间和麻烦。你能够经过在你本身的重载方法在加上 @total_ordering 来使用。

3.2 神奇方法——数字

就像你能够经过重载比较操做符的途径来建立你本身的类实例,你一样能够重载数字操做符。系好大家的安全带,朋友们,还有不少呢。处于本文组织的须要,我会把数字的神奇方法分割成5块:一元操做符,常规算术操做符,反射算术操做符,增量赋值,类型转换。

一元操做符

一元运算和函数仅有一个操做数,好比负数,绝对值等

__ pos__(self)

实现一元正数的行为(如:+some_object)

__ neg__(self)

实现负数的行为(如: -some_object)

__ abs__(self)

实现内建 abs() 函数的行为

__ invert__(self)

实现用~操做符进行的取反行为。你能够参考 Wiki:bitwise operations 来解释这个运算符究竟会干什么
常规算术操做符

如今咱们涵盖了基本的二元运算符:+,-,* 等等。其中大部分都是不言自明的。

__ add__(self, other)

实现加法

__ sub__(self, other)

实现减法

__ mul__(self, other)

实现乘法

__ floordiv__(self, other)

实现地板除法,使用 // 操做符

__ div__(self, other)

实现传统除法,使用 / 操做符

__ truediv__(self, other)

实现真正除法。注意,只有当你 from __ future__ import division 时才会有效

__ mod__(self, other)

实现求模,使用 % 操做符

__ divmod__(self, other)

实现内建函数 divmod() 的行为

__ pow__(self, other)

实现乘方,使用 ** 操做符

__ lshift__(self, other)

实现左按位位移,使用 << 操做符

__ rshift__(self, other)

实现右按位位移,使用 >> 操做符

__ and__(self, other)

实现按位与,使用 & 操做符

__ or__(self, other)

实现按位或,使用 | 操做符

__ xor__(self, other)

实现按位异或,使用 ^ 操做符

反射算术操做符

你知道我会如何解释反射算术操做符?大家中的有些人或许会以为它很大,很可怕,是国外的概念。但它实际上很简单,下面给一个例子:

some_object + other

这是“常规的”加法。而反射其实至关于一回事,除了操做数改变了改变下位置:

other + some_object

所以,全部这些神奇的方法会作一样的事等价于常规算术操做符,除了改变操做数的位置关系,好比第一个操做数和自身做为第二个。此外没有其余的操做方式。在大多数状况下,反射算术操做的结果等价于常规算术操做,因此你尽能够在刚重载完 __ radd__就调用 add。干脆痛快:

__radd__(self, other)

实现反射加法

__rsub__(self, other)

实现反射减法

__rmul__(self, other)

实现反射乘法

__rfloordiv__(self, other)

实现反射地板除,用 // 操做符

__rdiv__(self, other)

实现传统除法,用 / 操做符

__rturediv__(self, other)

实现真实除法,注意,只有当你 from __future__ import division 时才会有效

__rmod__(self, other)

实现反射求模,用 % 操做符

__rdivmod__(self, other)

实现内置函数 divmod() 的长除行为,当调用 divmod(other,self) 时被调用

__rpow__(self, other)

实现反射乘方,用 ** 操做符

__rlshift__(self, other)

实现反射的左按位位移,使用 << 操做符

__rrshift__(self, other)

实现反射的右按位位移,使用 >> 操做符

__rand__(self, other)

实现反射的按位与,使用 & 操做符

__ror__(self, other)

实现反射的按位或,使用 | 操做符

__rxor__(self, other)

实现反射的按位异或,使用 ^ 操做符

增量赋值

Python 也有各类各样的神奇方法容许用户自定义增量赋值行为。你可能已经熟悉增量赋值,它结合了“常规的”操做符和赋值。若是你仍不明白我在说什么,下面有一个例子:

x = 5
x += 1 # 等价 x = x + 1

这些方法都不会有返回值,由于赋值在 Python 中不会有任何返回值。反而它们只是改变类的状态。列表以下:

__iadd__(self, other)

实现加法和赋值

__isub__(self, other)

实现减法和赋值

__imul__(self, other)

实现乘法和赋值

__ifloordiv__(self, other)

实现地板除和赋值,用 //= 操做符

__idiv__(self, other)

实现传统除法和赋值,用 /= 操做符

__iturediv__(self, other)

实现真实除法和赋值,注意,只有当你 from __future__ import division 时才会有效

__imod__(self, other)

实现求模和赋值,用 %= 操做符

__ipow__(self, other)

实现乘方和赋值,用 **= 操做符

__ilshift__(self, other)

实现左按位位移和赋值,使用 <<= 操做符

__irshift__(self, other)

实现右按位位移和赋值,使用 >>= 操做符

__iand__(self, other)

实现按位与和赋值,使用 &= 操做符

__ior__(self, other)

实现按位或和赋值,使用 |= 操做符

__ixor__(self, other)

实现按位异或和赋值,使用 ^= 操做符

类型转换的神奇方法

Python 也有一组神奇方法被设计用来实现内置类型转换函数的行为,如 float()

__int__(self)

实现到 int 的类型转换

__long__(self)

实现到 long 的类型转换

__float__(self)

实现到 float 的类型转换

__complex__(self)

实现到复数的类型转换

__oct__(self)

实现到 8 进制的类型转换

__hex__(self)

实现到 16 进制的类型转换

__index__(self)

实现一个当对象被切片到 int 的类型转换。若是你自定义了一个数值类型,考虑到它可能被切片,因此你应该重载__index__

__trunc__(self)

当 math.trunc(self) 被调用时调用。__trunc__ 应当返回一个整型的截断,(一般是 long)

__coerce__(self, other)

该方法用来实现混合模式的算术。若是类型转换不可能那 __coerce__ 应当返回 None。 不然,它应当返回一对包含 self 和 other(2 元组),且调整到具备相同的类型

4.描述你的类

用一个字符串来讲明一个类这一般是有用的。 在 Python 中提供了一些方法让你能够在你本身的类中自定义内建函数返回你的类行为的描述。

__str__(self)

当你定义的类中一个实例调用了 str(),用于给它定义行为

__repr__(self)

当你定义的类中一个实例调用了 repr(),用于给它定义行为。 str() 和 repr() 主要的区别在于它的阅读对象。 repr() 产生的输出主要为计算机可读(在不少状况下,这甚至多是一些有效的 Python 代码),而 str() 则是为了让人类可读。

__unicode__(self)

当你定义的类中一个实例调用了 unicode(),用于给它定义行为。 unicode() 像是 str(),只不过它返回一个 unicode 字符串。 警戒!若是用户用你的类中的一个实例调用了 str(),而你仅定义了 __unicode__(),那它是不会工做的。 以防万一,你应当老是定义好 __str__(),哪怕用户不会使用 unicode

__hash__(self)

当你定义的类中一个实例调用了 hash(),用于给它定义行为。 它必须返回一个整型,并且它的结果是用于来在字典中做为快速键比对。

__nonzero__(self)

当你定义的类中一个实例调用了 bool(),用于给它定义行为。 返回 True 或 False,取决于你是否考虑一个实例是 True 或 False 的。

咱们已经至关漂亮地干完了神奇方法无聊的部分(无示例),至此咱们已经讨论了一些基础的神奇方法,是时候让咱们向高级话题移动了。

5.属性访问控制

有许多从其余语言阵营转到 Python 来的人抱怨 Python 对类缺少真正的封装(好比,没有办法自定义 private 属性,已经给出 public 的 getter 和 setter)。 这可不是真相哟:Python 经过神奇的方法实现了大量的封装,而不是经过明确的方法或字段修饰符。

请看:

__ getattr__(self, name)

你能够为用户在试图访问不存在(不管是存在或还没有创建)的类属性时定义其行为。 这对捕捉和重定向常见的拼写错误,给出使用属性警告是有用的(只要你愿意,你仍旧可选计算,返回那个属性)或抛出一个 AttributeError异常。 这个方法只适用于访问一个不存在的属性,因此,这不算一个真正封装的解决之道。

__ setattr__(self, name, value)

不像 __ getattr__, setattr 是一个封装的解决方案。 它容许你为一个属性赋值时候的行为,不论这个属性是否存在。 这意味着你能够给属性值的任意变化自定义规则。 然而,你须要在乎的是你要当心使用 setattr,在稍后的列表中会做为例子给出。

__ delattr__

这等价于 __ setattr__, 可是做为删除类属性而不是 set 它们。 它须要相同的预防措施,就像 setattr,防止无限递归(当在 delattr 中调用 del self.name 会引发无限递归)。

__ getattribute__(self, name)

__ getattribute__ 良好地适合它的同伴们 __ setattr__ 和 __ delattr__。 可我却不建议你使用它。 getattribute 只能在新式类中使用(在 Python 的最新版本中,全部的类都是新式类,在稍旧的版本中你能够经过继承 object 类来建立一个新式类。 它容许你定规则,在任什么时候候无论一个类属性的值那时候是否可访问的。) 它会由于他的同伴中的出错连坐受到某些无限递归问题的困扰(这时你能够经过调用基类的 getattribute 方法来防止发生)。 当 getattribute 被实现而又只调用了该方法若是 getattribute 被显式调用或抛出一个 AttributeError 异常,同时也主要避免了对 getattr 的依赖。 这个方法可使用(毕竟,这是你本身的选择),不过我不推荐它是由于它有一个小小的用例(虽然说比较少见,但咱们须要特殊行为以获取一个值而不是赋值)以及它真的很难作到实现 0bug。

你能够很容易地在你自定义任何类属性访问方法时引起一个问题。参考这个例子:

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__类属性):

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.制做自定义序列

颇有多种方式可让你的类表现得像内建序列(字典,元组,列表,字符串等)。 这些是我迄今为止最喜欢的神奇方法了,由于不合理的控制它们赋予了你一种魔术般地让你的类实例整个全局函数数组漂亮工做的方式。 在咱们开始讲解这个内容以前,让咱们先快速理清需求。
需求

如今咱们正在谈论如何建立你本身的序列。 也是什么谈一谈 protocol 了。 protocol 在某些地方跟接口很类似。 接口在其余语言中,是一组给定的方法,而你必须定义它们。 然而,在 Python 中 protocol 是彻底非正式的,并且不要求显式声明去实现。 更进一步说,它们更像是准则。

为什么咱们如今要谈论 protocol? 由于在 Python 中要实现自定义容器类型会涉及使用到这其中某些 protocol。

首先,有一个 protocol 是为定义不可变容器的:为了制做一个不可变容器,你只须要定义 __ len__ 和__ getitem__(稍后详述)。 可变容器 protocol 要求全部不可变容器增长 setitem delitem。 而后,若是你但愿你的对象是可迭代的,那你还得定义一个会返回迭代器 iterator 的 iter 方法。 而且这个迭代器必须遵照一个迭代 protocol,也就是要求迭代器有回调方法 iter (返回自身)和 next。
隐藏在容器背后的魔法

已经火烧眉毛了?如下即是容器使用的神奇魔法:

__ len__(self)

返回容器的长度。部分 protocol 同时支持可变和不可变容器

__ getitem__(self, key)

定义当某一个 item 被访问时的行为,使用 self[key] 表示法。 这个一样也是部分可变和不可变容器 protocol。 这也可抛出适当的异常: TypeError 当 key 的类型错误,或没有值对应 Key 时。

__ setitem__(self, key, value)

定义当某一个 item 被赋值时候的行为,使用 self[key]=value 表示法。 这也是部分可变和不可变容器 protocol。 再一次重申,你应当在适当之处抛出 KeyError 和 TypeError 异常。

__delitem__(self, key)

定义当某一个 item 被删除(例如 del self[key])时的行为。 这仅是部分可变容器的 protocol。在一个无效key 被使用后,你必须抛出一个合适的异常。

__ iter__(self)

应该给容器返回一个迭代器。 迭代器会返回若干内容,大多使用内建函数 iter() 表示。 当一个容器使用形如 for x in container: 的循环。 迭代器自己就是其对象,同时也要定义好一个 __iter__ 方法来返回自身。

__ reversed__(self)

当定义调用内建函数 reversed() 时的行为。应该返回一个反向版本的列表。

__ contains__(self, item)

__ contains__ 为成员关系,用 in 和 not in 测试时定义行为。 那你会问这个为什么不是一个序列的 protocol 的一部分? 这是由于当 __contains__ 未定义,Python 就会遍历序列,若是遇到正在寻找的 item 就会返回True。

__ concat__(self, other)

最后,你可经过 __concat__ 定义你的序列和另一个序列的链接。 应该从 self 和 other 返回一个新构建的序列。 当调用 2 个序列时 __concat__ 涉及操做符 +

一个例子

在咱们的例子中,让咱们看一下一个 list 实现的某些基础功能性的构建。 可能会让你想起你使用的其余语言(好比 Haskell)。

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]

经过这个(轻量的)有用的例子你知道了如何实现你本身的序列。 固然,还有不少更有用的应用,可是它们其中的不少已经被标准库实现了,像 Counter, OrderedDict, NamedTuple

7.反射

你也能够经过定义神奇方法来控制如何反射使用内建函数 isinstance() 和 issubclass() 的行为。 这些神奇方法是:

__instancecheck__(self, instance)

检查一个实例是不是你定义类中的一个实例(好比,isinstance(instance, class))

__subclasscheck__(self, subclass)

检查一个类是不是你定义类的子类(好比,issubclass(subclass, class))

这对于神奇方法的用例状况来讲可能较小,可它的确是真的。 我并不想花费太多的时间在反射方法上面,由于他们不是那么地重要。 不过它们反映了在 Python 中关于面对对象编程一些重要的东西,并且在 Python 中的广泛:老是在找一种简单的方式来作某些事情,即便它能被用到的很少。 这些神奇方法彷佛看上去不那么有用,但当你须要使用它们的时候你会感激它们的存在(和你阅读的这本指南!)。

8.可调用对象

正如你可能已经知道,在 Python 中函数是第一类对象。 这就意味着它们能够被传递到函数和方法,就像是任何类型的对象。 这真是一种难以置信强大的特性。

这是 Python 中一个特别的神奇方法,它容许你的类实例像函数。 因此你能够“调用”它们,把他们当作参数传递给函数等等。 这是另外一个强大又便利的特性让 Python 的编程变得更可爱了。

__ call__(self, [args...])

容许类实例像函数同样被调用。 本质上,这意味着 x() 等价于 x.__ call__()。 注意, call 须要的参数数目是可变的,也就是说能够对任何函数按你的喜爱定义参数的数目定义 call

__ call__ 可能对于那些常常改变状态的实例来讲是极其有用的。 “调用”实例是一种顺应直觉且优雅的方式来改变对象的状态。 下面一个例子是一个类表示一个实体在一个平面上的位置:

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 才做为第一个类语言结构取得了重要地位而被接受。 你有可能早就已经见识过 with 声明:

with open('foo.txt') as bar:
# 对bar执行某些动做

上下文管理容许对对象进行设置和清理动做,用 with 声明进行已经封装的操做。 上下文操做的行为取决于 2 个神奇方法:

__ enter__(self)

定义块用 with 声明建立出来时上下文管理应该在块开始作什么。 注意,enter 的返回值必须绑定 with 声明的目标,或是 as 后面的名称。

__ exit__(self, exception_type, exception_value, traceback)

定义在块执行(或终止)以后上下文管理应该作什么。 它能够用来处理异常,进行清理,或行动处于块以后某些老是被当即处理的事。 若是块执行成功的话,excepteion_type,exception_value,和 traceback 将会置None。 不然,你能够选择去处理异常,或者让用户本身去处理。 若是你想处理,确保在所有都完成以后__ exit__ 会返回 True。 若是你不想让上下文管理处理异常,那就让它发生好了。

__ enter__ 和 __ exit__ 对那些已有良好定义和对设置,清理行为有共同行为的特殊类是有用。 你也可使用这些方法去建立封装其余对象通用的上下文管理。 看下面的例子:

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 链接进行的演示(一个可关闭的套接字):

>>> 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__中的一个。 让咱们快点看一下这些神奇方法吧:

__ get__(self, instance, owner)

当描述符的值被取回时定义其行为。instance 是 owner 对象的一个实例,owner 是全部类。

__ set__(self, instance, value)

当描述符的值被改变时定义其行为。instance 是 owner 对象的一个实例,value 是设置的描述符的值

__ delete__(self, instance)

当描述符的值被删除时定义其行为。instance 是 owner 对象的一个实例。

如今,有一个有用的描述符应用例子:单位转换策略

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:盐水中的快速浸泡

让咱们跳入 pickling。 话说你有一个词典你想要保存它并在稍后取回。 你能够把它的内容写到一个文件中去,须要很是当心地确保你写了正确的语法,而后用 exec() 或处理文件的输入取回写入的内容。 但这是不稳定的:若是你你在纯文本中保存重要的数据,它有可能被几种方法改变,致使你的程序 crash 或在你的计算机上运行了恶意代码而出错。 因而,咱们准备 pickle 它:

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 它:

import pickle

pk1_file = open('data.pk1','rb') #链接pickled数据
data = pickle.load(pk1_file) #把数据load到一个变量中去
print data
pk1_file.close()

发生了什么事?正如你的预期,咱们得到了 data。

如今,我要给你一些忠告:pickling 并不是完美。 Pickle 文件很容易因意外或出于故意行为而被损毁。 Pickling 可能比起使用纯文本文件安全些,但它仍旧有可能会被用来跑恶意代码。 还有由于 Python 版本的不兼容问题,因此不要指望发布 Pickled 对象,也不要指望人们可以打开它们。 可是,它依然是一个强大的缓存工具和其余常见序列化任务。

Pickling你自定义的对象

Pickling 不只可用在内建类型上,还能够用于遵照 pickle 协议的任何类。 pickle 协议有 4 个可选方法用于定制 Python 对象如何运行(这跟 C 扩展有点不一样,但那不在咱们讨论的范围内):

__ getinitargs__(self)

若是你想当你的类 unpickled 时调用 __ init__,那你能够定义__ getinitargs__,该方法应该返回一个元组的参数,而后你能够把他传递给 __ init__。注意,该方法仅适用于旧式类。

__ getnewargs__(self)

对于新式类,你能够影响有哪些参数会被传递到 __new__ 进行 unpickling。 该方法一样应该返回一个元组参数,而后能传递给 __new__

__getstate__(self)

代替对象的 __dict__ 属性被保存。 当对象 pickled,你可返回一个自定义的状态被保存。 当对象 unpickled 时,这个状态将会被 __setstate__ 使用。

__setstate__(self, state)

对象 unpickled 时,若是 __setstate__ 定义对象状态会传递来代替直接用对象的 __dict__ 属性。 这正好跟__getstate__ 手牵手:当两者都被定义了,你能够描述对象的 pickled 状态,任何你想要的。

一个例子:

咱们的例子是 Slate 类,它会记忆它曾经的值和已经写入的值。 然而,当这特殊的 slate 每一次 pickle 都会被清空:当前值不会被保存。

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)` 序列化

但愿这张表格能够帮你扫清你有关语法涉及到神奇方法的问题。

相关文章
相关标签/搜索