虽着Python的学习不断的深入,我们要慢慢的不断的提升我们的硬件知识,在Python中,魔法方法是非常重要的一部分。虽然Python的魔法方法在Python文档中都能找到,但是我觉得太过于松散了,所以,我来总结一波Python的魔法方法,希望能给你带你帮助。最近迷上了脑图,发现了用思维导图来总结知识,线条会非常的清楚,以后我的总结应该都会基于思维导图来展示出来。如果大家有什么意见或者见解可以私信我!因为压缩原因,思维导图的图片比较模糊,要高清的图片或者pdf可以在微信搜cookdev,或者扫描下面图片,回复魔法方法即可获取到!
下面我们开始总结Python常见的一些魔法方法。
首先,说到魔法方法,那到底什么是魔法方法呢?
他们在面向对象的Python中处处皆是,他们是一些可以让我们的类添加”魔法“的特殊方法。她们经常是两个下划线保卫来命名的(比如\__init__)。好的,说完好像没说一样,这是官方的解答。我自己觉得,魔法方法就是可以给我们的类定制化更多的功能,设计出更符合我们需求的类。
我们一开始学习Python的时候,接触的基本魔法方法就是__init__,我们可以用它来指明一个对象的初始化行为。然而,当我们调用 x = A()的时候,__init__并不是第一个被调用的方法。第一个被调用的是 __new__ ,这个 方法才真正地创建了实例。当这个对象的生命周期结束的时候, __del__ 会被调用。让我们近一步理解这三个方法:
__new__ 是对象实例化时第一个调用的方法,它只取下 cls 参数,并把其他参数传给 __init__ 。 __new__ 很少使用,但是也有它适合的场景,尤其是当类继承自一个像元组或者字符串这样不经常改变的类型的时候。
__new__方法可以用于我们创建单例模式:
class A: instance = None def __new__(cls, *args, **kwargs): if cls.instance is None: cls.instance = super().__new__(cls) return cls.instance
__init__(self,[…])
类的初始化方法。它获取任何传给构造器的参数(比如我们调用 x = SomeClass(10, ‘foo’) , __init__ 就会接到参数 10 和 ‘foo’ 。 __init__ 在Python的类定义中用的最多。
__del__(self)
__new__ 和 __init__ 是对象的构造器, __del__ 是对象的销毁器。它并非实现了语句 del x (因此该语句不等同于 x.__del__())。而是定义了当对象被垃圾回收时的行为。 当对象需要在销毁时做一些处理的时候这个方法很有用,比如 socket 对象、文件对象。但是需要注意的是,当Python解释器退出但对象仍然存活的时候, __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
使用字符串来表示类是一个相当有用的特性。在Python中有一些内建方法可以返回类的表示,相对应的,也有一系列魔法方法可以用来自定义在使用这些内建函数时类的行为。
__str__(self)
定义对类的实例调用 str() 时的行为。
__repr__(self)
定义对类的实例调用 repr() 时的行为。 str() 和 repr() 最主要的差别在于“目标用户”。 repr() 的作用是产生机器可读的输出(大部分情况下,其输出可以作为有效的Python代码),而 str() 则产生人类可读的输出。
__unicode__(self)
定义对类的实例调用 unicode() 时的行为。 unicode() 和 str() 很像,只是它返回unicode字符串。注意,如果调用者试图调用 str() 而你的类只实现了 __unicode__() ,那么类将不能正常工作。所有你应该总是定义 __str__() ,以防有些人没有闲情雅致来使用unicode。
__format__(self)
定义当类的实例用于新式字符串格式化时的行为,例如, “Hello, 0:abc!”.format(a) 会导致调用 a.__format__(“abc”) 。当定义你自己的数值类型或字符串类型时,你可能想提供某些特殊的格式化选项,这种情况下这个魔法方法会非常有用。
__hash__(self)
定义对类的实例调用 hash() 时的行为。它必须返回一个整数,其结果会被用于字典中键的快速比较。同时注意一点,实现这个魔法方法通常也需要实现 __eq__ ,并且遵守如下的规则: a == b 意味着 hash(a) == hash(b)。
__nonzero__(self)
定义对类的实例调用 bool() 时的行为,根据你自己对类的设计,针对不同的实例,这个魔法方法应该相应地返回True或False。
__dir__(self)
定义对类的实例调用 dir() 时的行为,这个方法应该向调用者返回一个属性列表。一般来说,没必要自己实现 __dir__ 。但是如果你重定义了 __getattr__ 或者 __getattribute__ (下个部分会介绍),乃至使用动态生成的属性,以实现类的交互式使用,那么这个魔法方法是必不可少的。
很多从其他语言转向Python的人都抱怨Python的类缺少真正意义上的封装(即没办法定义私有属性然后使用公有的getter和setter)。然而事实并非如此。实际上Python不是通过显式定义的字段和方法修改器,而是通过魔法方法实现了一系列的封装。
当用户试图访问一个根本不存在(或者暂时不存在)的属性时,你可以通过这个魔法方法来定义类的行为。这个可以用于捕捉错误的拼写并且给出指引,使用废弃属性时给出警告(如果你愿意,仍然可以计算并且返回该属性),以及灵活地处理AttributeError。只有当试图访问不存在的属性时它才会被调用,所以这不能算是一个真正的封装的办法。
和 __getattr__ 不同, __setattr__ 可以用于真正意义上的封装。它允许你自定义某个属性的赋值行为,不管这个属性存在与否,也就是说你可以对任意属性的任何变化都定义自己的规则。然后,一定要小心使用 __setattr__ ,这个列表最后的例子中会有所展示。
这个魔法方法和 __setattr__ 几乎相同,只不过它是用于处理删除属性时的行为。和 _setattr__ 一样,使用它时也需要多加小心,防止产生无限递归(在 __delattr__ 的实现中调用 del self.name 会导致无限递归)。
\_\_getattribute\_\_
看起来和上面那些方法很合得来,但是最好不要使用它。 __getattribute__ 只能用于新式类。在最新版的Python中所有的类都是新式类,在老版Python中你可以通过继承 object 来创建新式类。 __getattribute__ 允许你自定义属性被访问时的行为,它也同样可能遇到无限递归问题(通过调用基类的 __getattribute__ 来避免)。 __getattribute__ 基本上可以替代 __getattr__ 。只有当它被实现,并且显式地被调用,或者产生 AttributeError 时它才被使用。 这个魔法方法可以被使用(毕竟,选择权在你自己),我不推荐你使用它,因为它的使用范围相对有限(通常我们想要在赋值时进行特殊操作,而不是取值时),而且实现这个方法很容易出现Bug。
自定义这些控制属性访问的魔法方法很容易导致问题,考虑下面这个例子:
def __setattr__(self, name. value): self.name = value # 因为每次属性幅值都要调用 \_\_setattr\_\_(),所以这里的实现会导致递归 # 这里的调用实际上是 self.\_\_setattr('name', value)。因为这个方法一直 # 在调用自己,因此递归将持续进行,直到程序崩溃 def __setattr__(self, name, value): self.__dict__[name] = value # 使用 \_\_dict\_\_ 进行赋值 # 定义自定义行为
再次重申,Python的魔法方法十分强大,能力越强责任越大,了解如何正确的使用魔法方法更加重要。
到这里,我们对Python中自定义属性存取控制有了什么样的印象?它并不适合轻度的使用。实际上,它有些过分强大,而且违反直觉。然而它之所以存在,是因为一个更大的原则:Python不指望让杜绝坏事发生,而是想办法让做坏事变得困难。自由是至高无上的权利,你真的可以随心所欲。下面的例子展示了实际应用中某些特殊的属性访问方法(注意我们之所以使用 super 是因为不是所有的类都有 __dict__ 属性):
class AccessCounter(object): ''' 一个包含了一个值并且实现了访问计数器的类 每次值的变化都会导致计数器自增''' 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) # 使计数器自增变成不可避免 # 如果你想阻止其他属性的赋值行为 # 产生 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)
你可以通过定义魔法方法来控制用于反射的内建函数 isinstance 和 issubclass 的行为。下面是对应的魔法方法:
__instancecheck__(self, instance)
检查一个实例是否是你定义的类的一个实例(例如 isinstance(instance, class) )。
__subclasscheck__(self, subclass)
检查一个类是否是你定义的类的子类(例如 issubclass(subclass, class) )。
这几个魔法方法的适用范围看起来有些窄,事实也正是如此。我不会在反射魔法方法上花费太多时间,因为相比其他魔法方法它们显得不是很重要。但是它们展示了在Python中进行面向对象编程(或者总体上使用Python进行编程)时很重要的一点:不管做什么事情,都会有一个简单方法,不管它常用不常用。这些魔法方法可能看起来没那么有用,但是当你真正需要用到它们的时候,你会感到很幸运,因为它们还在那儿
你可能已经知道了,在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
在Python 2.5中引入了一个全新的关键词,随之而来的是一种新的代码复用方法—— with 声明。上下文管理的概念在Python中并不是全新引入的(之前它作为标准库的一部分实现),直到PEP 343被接受,它才成为一种一级的语言结构。可能你已经见过这种写法了:
with open('foo.txt') as bar: # 使用bar进行某些操作
当对象使用 with 声明创建时,上下文管理器允许类做一些设置和清理工作。上下文管理器的行为由下面两个魔法方法所定义:
__enter__(self)
定义使用 with 声明创建的语句块最开始上下文管理器应该做些什么。注意 __enter__ 的返回值会赋给 with 声明的目标,也就是 as 之后的东西。
__exit__(self, exception_type, exception_value, traceback)
定义当 with 声明语句块执行完毕(或终止)时上下文管理器的行为。它可以用来处理异常,进行清理,或者做其他应该在语句块结束之后立刻执行的工作。如果语句块顺利执行, exception_type , exception_value 和 traceback 会是 None 。否则,你可以选择处理这个异常或者让用户来处理。如果你想处理异常,确保 __exit__ 在完成工作之后返回 True 。如果你不想处理异常,那就让它发生吧。
对一些具有良好定义的且通用的设置和清理行为的类,__enter__ 和 __exit__ 会显得特别有用。你也可以使用这几个方法来创建通用的上下文管理器,用来包装其他对象。下面是一个例子:
class Closer: '''一个上下文管理器,可以在with语句中 使用close()自动关闭对象''' def __init__(self, obj): self.obj = obj def __enter__(self, obj): return self.obj # 绑定到目标 def __exit__(self, exception_type, exception_value, traceback): try: self.obj.close() except AttributeError: # obj不是可关闭的 print 'Not closable.' return True # 成功地处理了异常
这是一个 Closer 在实际使用中的例子,使用一个FTP连接来演示(一个可关闭的socket):
>>> from magicmethods import Closer >>> from ftplib import FTP >>> with Closer(FTP('ftp.somesite.com')) as conn: ... conn.dir() ... # 为了简单,省略了某些输出 >>> conn.dir() # 很长的 AttributeError 信息,不能使用一个已关闭的连接 >>> with Closer(int(5)) as i: ... i += 1 ... Not closable. >>> i 6
看到我们的包装器是如何同时优雅地处理正确和不正确的调用了吗?这就是上下文管理器和魔法方法的力量。Python标准库包含一个 contextlib 模块,里面有一个上下文管理器 contextlib.closing() 基本上和我们的包装器完成的是同样的事情(但是没有包含任何当对象没有close()方法时的处理)。
描述符是一个类,当使用取值,赋值和删除 时它可以改变其他对象。描述符不是用来单独使用的,它们需要被一个拥有者类所包含。描述符可以用来创建面向对象数据库,以及创建某些属性之间互相依赖的类。描述符在表现具有不同单位的属性,或者需要计算的属性时显得特别有用(例如表现一个坐标系中的点的类,其中的距离原点的距离这种属性)。
要想成为一个描述符,一个类必须具有实现 __get__ , __set__ 和 __delete__ 三个方法中至少一个。
让我们一起来看一看这些魔法方法:
__get__(self, instance, owner)
定义当试图取出描述符的值时的行为。 instance 是拥有者类的实例, owner 是拥有者类本身。
__set__(self, instance, owner)
定义当描述符的值改变时的行为。 instance 是拥有者类的实例, value 是要赋给描述符的值。
__delete__(self, instance, owner)
定义当描述符的值被删除时的行为。 instance 是拥有者类的实例
现在,来看一个描述符的有效应用:单位转换:
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, owner): 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): '''用于描述距离的类,包含英尺和米两个描述符。''' meter = Meter() foot = Foot()
有些时候,特别是处理可变对象时,你可能想拷贝一个对象,改变这个对象而不影响原有的对象。这时就需要用到Python的 copy 模块了。然而(幸运的是),Python模块并不具有感知能力, 因此我们不用担心某天基于Linux的机器人崛起。但是我们的确需要告诉Python如何有效率地拷贝对象。
__copy__(self)
定义对类的实例使用 copy.copy() 时的行为。 copy.copy() 返回一个对象的浅拷贝,这意味着拷贝出的实例是全新的,然而里面的数据全都是引用的。也就是说,对象本身是拷贝的,但是它的数据还是引用的(所以浅拷贝中的数据更改会影响原对象)。
__deepcopy__(self, memodict=)
定义对类的实例使用 copy.deepcopy() 时的行为。 copy.deepcopy() 返回一个对象的深拷贝,这个对象和它的数据全都被拷贝了一份。 memodict 是一个先前拷贝对象的缓存,它优化了拷贝过程,而且可以防止拷贝递归数据结构时产生无限递归。当你想深拷贝一个单独的属性时,在那个属性上调用 copy.deepcopy() ,使用 memodict 作为第一个参数。
这些魔法方法有什么用武之地呢?像往常一样,当你需要比默认行为更加精确的控制时。例如,如果你想拷贝一个对象,其中存储了一个字典作为缓存(可能会很大),拷贝缓存可能是没有意义的。如果这个缓存可以在内存中被不同实例共享,那么它就应该被共享。
操作符的使用比较少,具体的各种用法,可以来我的github上看,有需要就可以来查询!
一些魔法方法直接和内建函数对应,这种情况下,如何调用它们是显而易见的。然而,另外的情况下,调用魔法方法的途径并不是那么明显。这个附录旨在展示那些不那么明显的调用魔法方法的语法。
魔法方法 | 什么时候被调用 | 解释 |
---|---|---|
__new__(cls [,…]) | instance = MyClass(arg1, arg2) | __new__在实例创建时调用 |
__init__(self [,…]) | instance = MyClass(arg1,arg2) | __init__在实例创建时调用 |
__cmp__(self) | 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) | self.name = val | 给属性赋值 |
__delattr_(self, name) | del self.name | 删除属性 |
__getattribute__(self,name) | self.name | 访问任意属性 |
__getitem__(self, key) | self[key] | 使用索引访问某个元素 |
__setitem__(self, key) | 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进行成员测试 |
__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) | Pickling |
__setstate__(self) | data = pickle.load(pkl_file) | Pickling |
本篇文章大部分来自一篇外国的文章,用geogle翻译了一下,并且增加了一些内容和总结带给大家。
最后加一句,学Python就来PythonGuide:https://github.com/hellgoddess/PythonGuide
「Python学习+面试指南」一份涵盖大部分Python相关行业的程序员所需要掌握的核心知识。准备Python面试,来看PythonGuide!
我会不断的更新和维护,希望能给大家带来帮助!
如果文章对你有用,点个赞与收藏吧,谢谢你!