Python 魔术方法指南.docx

上传人:b****5 文档编号:5649870 上传时间:2022-12-30 格式:DOCX 页数:17 大小:30.28KB
下载 相关 举报
Python 魔术方法指南.docx_第1页
第1页 / 共17页
Python 魔术方法指南.docx_第2页
第2页 / 共17页
Python 魔术方法指南.docx_第3页
第3页 / 共17页
Python 魔术方法指南.docx_第4页
第4页 / 共17页
Python 魔术方法指南.docx_第5页
第5页 / 共17页
点击查看更多>>
下载资源
资源描述

Python 魔术方法指南.docx

《Python 魔术方法指南.docx》由会员分享,可在线阅读,更多相关《Python 魔术方法指南.docx(17页珍藏版)》请在冰豆网上搜索。

Python 魔术方法指南.docx

Python魔术方法指南

Python魔术方法指南

入门

构造和初始化

构造定制类

▪用于比较的魔术方法

▪用于数值处理的魔术方法

表现你的类

控制属性访问

创建定制序列

反射

可以调用的对象

会话管理器

创建描述器对象

持久化对象

总结

附录

介绍

此教程为我的数篇文章中的一个重点。

主题是魔术方法。

什么是魔术方法?

他们是面向对象的Python的一切。

他们是可以给你的类增加”magic”的特殊方法。

他们总是被双下划线所包围(e.g.__init__或者__lt__)。

然而他们的文档却远没有提供应该有的内容。

Python中所有的魔术方法均在Python官方文档中有相应描述,但是对于他们的描述比较混乱而且组织比较松散。

很难找到有一个例子(也许他们原本打算的很好,在开始语言参考中有描述很详细,然而随之而来的确是枯燥的语法描述等等)。

所以,为了修补我认为Python文档应该修补的瑕疵,我决定给Python中的魔术方法提供一些用平淡的语言和实例驱使的文档。

我在开始已经写了数篇博文,现在在这篇文章中对他们进行总结。

我希望你能够喜欢这篇文章。

你可以将之当做一个教程,一个补习资料,或者一个参考。

本文章的目的仅仅是为Python中的魔术方法提供一个友好的教程。

构造和初始化

每个人都知道一个最基本的魔术方法,__init__。

通过此方法我们可以定义一个对象的初始操作。

然而,当我调用x=SomeClass()的时候,__init__并不是第一个被调用的方法。

实际上,还有一个叫做__new__的方法,来构造这个实例。

然后给在开始创建时候的初始化函数来传递参数。

在对象生命周期的另一端,也有一个__del__方法。

我们现在来近距离的看一看这三个方法:

__new__(cls,[...)__new__是在一个对象实例化的时候所调用的第一个方法。

它的第一个参数是这个类,其他的参数是用来直接传递给__init__方法。

__new__方法相当不常用,但是它有自己的特性,特别是当继承一个不可变的类型比如一个tuple或者string。

我不希望在__new__上有太多细节,因为并不是很有用处,但是在Python文档中有详细的阐述。

__init__(self,[…)此方法为类的初始化方法。

当构造函数被调用的时候的任何参数都将会传给它。

(比如如果我们调用x=SomeClass(10,'foo')),那么__init__将会得到两个参数10和foo。

__init__在Python的类定义中被广泛用到。

__del__(self)如果__new__和__init__是对象的构造器的话,那么__del__就是析构器。

它不实现语句delx(所以代码将不会翻译为x.__del__())。

它定义的是当一个对象进行垃圾回收时候的行为。

当一个对象在删除的时候需要更多的清洁工作的时候此方法会很有用,比如套接字对象或者是文件对象。

注意,因为当解释器退出的时候如果对象还存在,不能保证__del__能够被执行,所以__del__can’tserveasareplacementforgoodcodingpractices()~~~~~~~

放在一起的话,这里是一个__init__和__del__实际使用的例子。

fromos.pathimportjoin

classFileObject:

'''给文件对象进行包装从而确认在删除时文件流关闭'''

def__init__(self,filepath='~',filename='sample.txt'):

#读写模式打开一个文件

self.file=open(join(filepath,filename),'r+')

def__del__(self):

self.file.close()

delself.file

让定制的类工作起来

使用Python的魔术方法的最大优势在于他们提供了一种简单的方法来让对象可以表现的像内置类型一样。

那意味着你可以避免丑陋的,违反直觉的,不标准的的操作方法。

在一些语言中,有一些操作很常用比如:

ifinstance.equals(other_instance):

#dosomething

在Python中你可以这样。

但是这会让人迷惑且产生不必要的冗余。

相同的操作因为不同的库会使用不同的名字,这样会产生不必要的工作。

然而有了魔术方法的力量,我们可以定义一个方法(本例中为__eq__),就说明了我们的意思:

ifinstance==other_instance:

#dosomething

这只是魔术方法的功能的一小部分。

它让你可以定义符号的含义所以我们可以在我们的类中使用。

就像内置类型一样。

用于比较的魔术方法

Python对实现对象的比较,使用魔术方法进行了大的逆转,使他们非常只管而不是笨拙的方法调用。

而且还提供了一种方法可以重写Python对对象比较的默认行为(通过引用)。

以下是这些方法和他们的作用。

__cmp__(self,other)__cmp__是最基本的用于比较的魔术方法。

它实际上实现了所有的比较符号(<,==,!

=,etc.),但是它的表现并不会总是如你所愿(比如,当一个实例与另一个实例相等是通过一个规则来判断,而一个实例大于另外一个实例是通过另外一个规则来判断)。

如果selfother的时候会返回正数。

通常最好的一种方式是去分别定义每一个比较符号而不是一次性将他们都定义。

但是__cmp__方法是你想要实现所有的比较符号而一个保持清楚明白的一个好的方法。

__eq__(self,other)定义了等号的行为,==。

__ne__(self,other)定义了不等号的行为,!

=。

__lt__(self,other)定义了小于号的行为,<。

__gt__(self,other)定义了大于等于号的行为,>=。

举一个例子,创建一个类来表现一个词语。

我们也许会想要比较单词的字典序(通过字母表),通过默认的字符串比较的方法就可以实现,但是我们也想要通过一些其他的标准来实现,比如单词长度或者音节数量。

在这个例子中,我们来比较长度实现。

以下是实现代码:

classWord(str):

'''存储单词的类,定义比较单词的几种方法'''

def__new__(cls,word):

#注意我们必须要用到__new__方法,因为str是不可变类型

#所以我们必须在创建的时候将它初始化

if''inword:

print"Valuecontainsspaces.Truncatingtofirstspace."

word=word[:

word.index('')]#单词是第一个空格之前的所有字符

returnstr.__new__(cls,word)

def__gt__(self,other):

returnlen(self)>len(other)

def__lt__(self,other):

returnlen(self)

def__ge__(self,other):

returnlen(self)>=len(other)

def__le__(self,other):

returnlen(self)<=len(other)

现在,我们创建两个Words对象(通过使用Word('foo')和Word('bar')然后通过长度来比较它们。

注意,我们没有定义__eq__和__ne__方法。

这是因为将会产生一些怪异的结果(比如Word('foo')==Word('bar')将会返回true)。

这对于测试基于长度的比较不是很有意义。

所以我们退回去,用str内置来进行比较。

现在你知道你不必定义每一个比较的魔术方法从而进行丰富的比较。

标准库中很友好的在functiontols中提供给我们一个类的装饰器定义了所有的丰富的比较函数。

如果你只是定义__eq__和另外一个(e.g.__gt__,__lt__,etc.)这个特性仅仅在Python2.7中存在,但是你如果有机会碰到的话,那么将会节省大量的时间和经理。

你可以通过在你定义的类前放置@total_ordering来使用。

数值处理的魔术方法

如同你在通过比较符来比较类的实例的时候来创建很多方法,你也可以定义一些数值符号的特性。

系紧你的安全带,来吧,这里有很多内容。

为了组织方便,我将会把数值处理的方法来分成五类:

一元操作符,普通算数操作符,反射算数操作符(之后会详细说明),增量赋值,和类型转换。

一元操作符和函数

仅仅有一个操作位的一元操作符和函数。

比如绝对值,负等。

__pos__(self)实现正号的特性(比如+some_object)

__neg__(self)实现负号的特性(比如-some_object)

__abs__(self)实现内置abs()函数的特性。

__invert__(self)实现~符号的特性。

为了说明这个特性。

你可以查看Wikipedia中的这篇文章

普通算数操作符

现在我们仅仅覆盖了普通的二进制操作符:

+,-,*和类似符号。

这些符号大部分来说都浅显易懂。

__add__(self,other)实现加法。

__sub__(self,other)实现减法。

__mul__(self,other)实现乘法。

__floordiv__(self,other)实现//符号实现的整数除法。

__div__(self,other)实现/符号实现的除法。

__truediv__(self,other)实现真除法。

注意只有只用了from__future__importdivision的时候才会起作用。

__mod__(self,other)实现取模算法%__divmod___(self,other)实现内置divmod()算法__pow__实现使用**的指数运算__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)实现/符号的反除__rtruediv__(self,other)实现反真除,只有当from__future__importdivision的时候会起作用__rmod__(self,other)实现%符号的反取模运算__rdivmod__(self,other)当divmod(other,self)被调用时,实现内置divmod()的反运算__rpow__实现**符号的反运算__rlshift__(self,other)实现<<符号的反左位移__rrshift__(self,other)实现>>符号的反右位移__rand__(self,other)实现&符号的反与运算__ror__(self,other)实现|符号的反或运算__xor__(self,other)实现^符号的反异或运算

增量赋值

Python也有大量的魔术方法可以来定制增量赋值语句。

你也许对增量赋值已经很熟悉,它将操作符与赋值来结合起来。

如果你仍然不清楚我在说什么的话,这里有一个例子:

x=5

x+=1#inotherwordsx=x+1

__iadd__(self,other)实现赋值加法__isub__(self,other)实现赋值减法__imul__(self,other)实现赋值乘法__ifloordiv__(self,other)实现//=的赋值地板除__idiv__(self,other)实现符号/=的赋值除__itruediv__(self,other)实现赋值真除,只有使用from__future__importdivision的时候才能使用__imod_(self,other)实现符号%=的赋值取模__ipow__实现符号**=的赋值幂运算__ilshift__(self,other)实现符号<<=的赋值位左移__irshift__(self,other)实现符号>>=的赋值位右移__iand__(self,other)实现符号&=的赋值位与__ior__(self,other)实现符号|=的赋值位或__ixor__(self,other)实现符号|=的赋值位异或

类型转换魔术方法

Python也有很多的魔术方法来实现类似float()的内置类型转换特性。

__int__(self)实现整形的强制转换__long__(self)实现长整形的强制转换__float__(self)实现浮点型的强制转换__complex__(self)实现复数的强制转换__oct__(self)实现八进制的强制转换__hex__(self)实现二进制的强制转换__index__(self)当对象是被应用在切片表达式中时,实现整形强制转换,如果你定义了一个可能在切片时用到的定制的数值型,你应该定义__index__(详见PEP357)__trunc__(self)当使用math.trunc(self)的时候被调用。

__trunc__应该返回数值被截取成整形(通常为长整形)的值__coerce__(self,other)实现混合模式算数。

如果类型转换不可能的话,那么__coerce__将会返回None,否则他将对self和other返回一个长度为2的tuple,两个为相同的类型。

表现你的类

如果有一个字符串来表示一个类将会非常有用。

在Python中,有很多方法可以实现类定义内置的一些函数的返回值。

__str__(self)定义当str()调用的时候的返回值__repr__(self)定义repr()被调用的时候的返回值。

str()和repr()的主要区别在于repr()返回的是机器可读的输出,而str()返回的是人类可读的。

__unicode__(self)定义当unicode()调用的时候的返回值。

unicode()和str()很相似,但是返回的是unicode字符串。

注意,如a果对你的类调用str()然而你只定义了__unicode__(),那么将不会工作。

你应该定义__str__()来确保调用时能返回正确的值。

__hash__(self)定义当hash()调用的时候的返回值,它返回一个整形,用来在字典中进行快速比较__nonzero__(self)定义当bool()调用的时候的返回值。

本方法应该返回True或者False,取决于你想让它返回的值。

控制属性访问

许多从其他语言转到Python的人会抱怨它缺乏类的真正封装。

(没有办法定义私有变量,然后定义公共的getter和setter)。

Python其实可以通过魔术方法来完成封装。

我们来看一下:

__getattr__(self,name)你可以定义当用户试图获取一个不存在的属性时的行为。

这适用于对普通拼写错误的获取和重定向,对获取一些不建议的属性时候给出警告(如果你愿意你也可以计算并且给出一个值)或者处理一个AttributeError。

只有当调用不存在的属性的时候会被返回。

然而,这不是一个封装的解决方案。

__setattr__(self,name,value)与__getattr__不同,__setattr__是一个封装的解决方案。

无论属性是否存在,它都允许你定义对对属性的赋值行为,以为这你可以对属性的值进行个性定制。

但是你必须对使用__setattr__特别小心。

之后我们会详细阐述。

__delattr__与__setattr__相同,但是功能是删除一个属性而不是设置他们。

注意与__setattr__相同,防止无限递归现象发生。

(在实现__delattr__的时候调用delself.name即会发生)__getattribute__(self,name)__getattribute__与它的同伴__setattr__和__delattr__配合非常好。

但是我不建议使用它。

只有在新类型类定义中才能使用__getattribute__(在最新版本Python中所有的类都是新类型,在老版本中你可以通过继承object来制作一个新类。

这样你可以定义一个属性值的访问规则。

有时也会产生一些帝归现象。

(这时候你可以调用基类的__getattribute__方法来防止此现象的发生。

)它可以消除对__getattr__的使用,如果它被明确调用或者一个AttributeError被抛出,那么当实现__getattribute__之后才能被调用。

此方法是否被使用其实最终取决于你的选择。

)我不建议使用它因为它的使用几率较小(我们在取得一个值而不是设置一个值的时候有特殊的行为是非常罕见的。

)而且它不能避免会出现bug。

在进行属性访问控制定义的时候你可能会很容易的引起一个错误。

考虑下面的例子。

def__setattr__(self,name,value):

self.name=value

#每当属性被赋值的时候,``__setattr__()``会被调用,这样就造成了递归调用。

#这意味这会调用``self.__setattr__('name',value)``,每次方法会调用自己。

这样会造成程序崩溃。

def__setattr__(self,name,value):

self.__dict__[name]=value#给类中的属性名分配值

#定制特有属性

Python的魔术方法非常强大,然而随之而来的则是责任。

了解正确的方法去使用非常重要。

所以我们对于定制属性访问权限了解了多少呢。

它不应该被轻易的使用。

实际上,它非常强大。

但是它存在的原因是:

Python不会试图将一些不好的东西变得不可能,而是让它们难以实现。

自由是至高无上的,所以你可以做任何你想做的。

一下是一个特别的属性控制的例子(我们使用super因为不是所有的类都有__dict__属性):

classAccessCounter:

'''一个包含计数器的控制权限的类每当值被改变时计数器会加一'''

def__init__(self,val):

super(AccessCounter,self).__setattr__('counter',0)

super(AccessCounter,self).__setattr__('value',val)

def__setattr__(self,name,value):

ifname=='value':

super(AccessCounter,self).__setattr__('counter',self.counter+1)

#如果你不想让其他属性被访问的话,那么可以抛出AttributeError(name)异常

super(AccessCounter,self).__setattr__(name,value)

def__delattr__(self,name):

ifname=='value':

super(AccessCounter,self).__setattr__('counter',self.counter+1)

super(AccessCounter,self).__delattr__(name)]

创建定制的序列

有很多方法让你的Python类行为可以像内置的序列(dict,tuple,list,string等等)。

这是目前位置我最喜欢的魔术方法因为它给你很搞的控制权限而且让很多函数在你的类实例上工作的很出色。

但是在开始之前,需要先讲一些必须条件。

必须条件

现在我们开始讲如何在Python中创建定制的序列,这个时候该讲一讲协议。

协议(Protocols)与其他语言中的接口很相似。

它给你很多你必须定义的方法。

然而在Python中的协议是很不正式的,不需要明确声明实现。

事实上,他们更像一种指南。

我们为什么现在讨论协议?

因为如果要定制容器类型的话需要用到这些协议。

首先,实现不变容器的话有一个协议:

实现不可变容器,你只能定义__len__和__getitem__(一会会讲更多)。

可变容器协议则需要所有不可变容器的所有另外还需要__setitem__和__delitem__。

最终,如果你希望你的对象是可迭代的话,你需要定义__iter__会返回一个迭代器。

迭代器必须遵循迭代器协议,需要有__iter__(返回它本身)和next。

容器后的魔法

这些是容器使用的魔术方法。

__len__(self)然会容器长度。

对于可变不可变容器都需要有的协议的一部分。

__getitem__(self,key)定义当一个条目被访问时,使用符号self[key]。

这也是不可变容器和可变容器都要有的协议的一部分。

如果键的类型错误和KeyError或者没有合适的值。

那么应该抛出适当的TypeError异常。

__setitem__(self,key,value)定义当一个条目被赋值时的行为,使用self[key]=value。

这也是可变容器和不可变容器协议中都要有的一部分。

__delitem__(self,key)定义当一个条目被删除时的行为(比如delself[key])。

这只是可变容器协议中的一部分。

当使用一个无效的键时应该抛出适当的异常。

__iter__(self)返回一个容器的迭代器。

很多情况下会返回迭代器,尤其是当内置的iter()方法被调用的时候,或者当使用forxincontai

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > 医药卫生 > 基础医学

copyright@ 2008-2022 冰豆网网站版权所有

经营许可证编号:鄂ICP备2022015515号-1