Python3 魔法方法详解
魔法方法就是可以给你的类增加魔力的特殊方法,如果你的对象实现(重载)了这些方法中的某一个,那么这个方法就会在特殊的情况下被 Python 所调用,你可以定义自己想要的行为,而这一切都是自动触发的。它们经常是两个下划线包围来命名的(比如 __init__
,__lt__
),Python的魔法方法是非常强大的,所以了解其使用方法也变得尤为重要!
1.__init__
构造函数,创建对象时自动调用,初始化对象,默认返回None,不用写return。
def __init__(self):pass #self代表实例,而非类。
如果类有继承,也可以这样初始化。def __init__(self): super().__init__().在子类中没有__init__,不用写,自动回去调用。但是在子类中如果有init,还想去调用父类的init,就要写上了。
class a: def __init__(self,name): self.name = name class b(a): def __init__(self,name,age): self.age = age super().__init__(name) test = b('lili',18) print(test.age,test.name)
2.__new__
__new__方法只负责创建,返回一个空对象,然后由__init__来初始化。
__new__至少要有一个参数cls,代表要实例化的类,此参数在实例化时由Python解释器自动提供
__new__必须要有返回值,返回实例化出来的实例,这点在自己实现__new__时要特别注意,可以return父类__new__出来的实例,或者直接是object的__new__出来的实例。
实际中,你很少会用到__new__
,除非你希望能够控制类的创建,这时需要牵扯到metaclass
(元类)的介绍。
3.__del__ 销毁对象,(对象占用的内存被回收)
在对象的生命周期结束时, __del__
会被调用,可以将__del__
理解为"析构函数"。__del__
定义的是当一个对象进行垃圾回收时候的行为。
del语句作用在变量上,而不是数据对象上。
if __name__=='__main__': a=1 # 对象 1 被 变量a引用,对象1的引用计数器为1 b=a # 对象1 被变量b引用,对象1的引用计数器加1 c=a #1对象1 被变量c引用,对象1的引用计数器加1 del a #删除变量a,解除a对1的引用.
del b #删除变量b,解除b对1的引用 print(c) #最终变量c仍然引用1
__del__()和__delattr__()的区别
del语句作用于类实例和类实例属性是两种不同的情况,前者触发__del__(),而后者才触发__delattr__(),测试代码如下:
class Fun: def __init__(self): self.name = "Tom Ford" def __del__(self): print("calling __del__") def __delattr__(self, name): print("calling __delattr__") fun = Fun() del fun.name del fun # 输出如下: calling __delattr__ calling __del__
delitem与delattr区别
class Foo: def __init__(self, name): self.name = name def __getitem__(self, item): print('===>', self.__dict__[item]) def __setitem__(self, key, value): self.__dict__[key] = value def __delitem__(self, key): print('del obj[key]时,我执行') self.__dict__.pop(key) def __delattr__(self, item): print('del obj.key时,我执行') self.__dict__.pop(item) f1 = Foo('a') f1['age'] = 18 f1['age1'] = 19 del f1.age1 del f1['age'] # setitem f1['name'] = 'a' # getitem f1['name'] delattr: del f1.name触发delattr; delitem: del f1[‘name’]触发delitem; 能写成f1[‘name’]的分为两种情况: 字典 def __setitem__(self) : pass
总结:del 对象名,调用__del__,删除对象;
del 对象名.属性,调用__delattr__;(类属性也一样)
del 对象名['属性名'],调用的是__delitem__;下面两个删除属性
如果m.x实现了描述符,del 对象名.属性,将会调用描述符中的__delete__
4.__str__ 定义当前类的实例的文本显示内容
对实例使用str()
时调用。
""" 解释: 当实例化的类对象 被str() 函数调用时 自动触发此方法 作用: """ class Str_(object): def __str__(self): print('你调用了str 方法!') return '返回值必须为字符串' s = Str_() str( s )
5.__str__/__repr__
__str__定义对类的实例调用str()时的行为。
而__repr__定义对类的实例调用repr()的行为,
这两者的区别就是repr面向机器,str面向人。定义类的输出的时候经常会使用这两个其中的魔法。
class a: def __str__(self): return "str" def __repr__(self): return "repr" b = a() print(b)
print(str(b)) print(repr(b))
一般这样:__str__ = __repr__
上面例子中若没有__str__,输出将是一样的。
扩展:
__unicode__(self) #python3中删除了。
对实例使用unicode()
时调用。unicode()
与str()
的区别在于: 前者返回值是unicode, 后者返回值是str。unicode和str都是basestring
的子类。
当你对一个类只定义了__str__
但没定义__unicode__
时,__unicode__
会根据__str__
的返回值自动实现,即return unicode(self.__str__())
;
但返回来则不成立
class c: pass class a(c,object): # def __str__(self): # return "str" def __repr__(self): return "repr" b = a() print(b.__class__)#b.__class__.__name__ print(a.__base__) print(a.__bases__)
<class '__main__.a'> <class '__main__.c'> (<class '__main__.c'>, <class 'object'>)
7.__call__(self, *args)
对象后面加括号,触发执行。
注:构造方法的执行是由创建对象触发的,即:对象 = 类名() ;而对于 __call__ 方法的执行是由对象后加括号触发的,即:对象() 或者 类()()
class Foo: def __call__(self, *args, **kwargs): print('实例执行 obj()') f1 = Foo() f1() # 调用Foo类下的__call__() Foo() # 触发__call__()方法,一切接对象
8.__len__(self)
定义当被 len() 调用时的行为。len()适合于list,dict,set,str等形式。自定义类想用,必须重写len 的方法。
9.__bytes__(self)
定义当被 bytes() 调用时的行为。bytes()参见博客内置方法。https://www.cnblogs.com/yunlong-study/p/14325878.html
python3只能通过二进制(bytes)方式来传输,因此要对传输文本进行转换。
10.__dict__
可以看作是数据对象的名称空间,所以只包含自己的属性,且可以直接增、删、改、查__dict__
。
dir()可以看作是显示属性的包含显示,除了显示自己的还显示继承来的属性。
__dict__
是个dict结构,仅仅只显示完全独属于自己的属性
dir()
是一个list结构,除了显示自己的属性,还显示父类继承而来的属性,比如从祖先类object中继承的属性
__dict__
是一个dict,它和数据对象的属性直接关联,可以直接通过__dict__
访问、设置、修改、删除属性,比如类的对象实例可以通过self.x=3
设置x属性,也可以通过__dict__['x']=3
来设置属性x。
而dir()函数仅仅只是展现一些属性。
11.类型转换
-
__int__(self)
实现到int的类型转换。内置方法int()
-
__long__(self)
实现到long的类型转换。
-
__float__(self)
实现到float的类型转换。内置方法float()
-
__complex__(self)
实现到complex的类型转换。内置方法complex()
-
__oct__(self)
实现到八进制数的类型转换。内置方法oct()
-
__hex__(self)
实现到十六进制数的类型转换。内置方法hex()
-
__index__(self)
首先,这个方法应该返回一个整数,可以是int或者long。这个方法在两个地方有效,首先是 operator 模块中的index函数得到的值就是这个方法的返回值,其次是用于切片操作
-
__trunc__(self)
当调用 math.trunc(self) 时调用该方法, __trunc__ 应该返回 self 截取到一个整数类型(通常是long类型)的值。
- __round__
定义当被 round() 调用时的行为(需要返回恰当的值)
round() 方法调用的是__round__,返回浮点数 x 的四舍五入值,准确的说保留值将保留到离上一位更近的一端(四舍六入)。
精度要求高的,不建议使用该函数。
-
__coerce__(self) #因存在冗余而废弃.
该方法用于实现混合模式算数运算,如果不能进行类型转换, __coerce__ 应该返回 None 。反之,它应该返回一个二元组 self 和 other ,这两者均已被转换成相同的类型。
__getattr__(self, name)
该方法定义了你试图访问一个不存在的属性时的行为。因此,重载该方法可以实现捕获错误拼写然后进行重定向, 或者对一些废弃的属性进行警告。
__setattr__(self, name, value)
__setattr__
是实现封装的解决方案,它定义了你对属性进行赋值和修改操作时的行为。
不管对象的某个属性是否存在,它都允许你为该属性进行赋值,因此你可以为属性的值进行自定义操作。有一点需要注意,实现__setattr__
时要避免"无限递归"的错误,下面的代码示例中会提到。
__delattr__(self, name)
__delattr__
与__setattr__
很像,只是它定义的是你删除属性时的行为。实现__delattr__
是同时要避免"无限递归"的错误。
__getattribute__(self, name)
__getattribute__
定义了你的属性被访问时的行为,相比较,__getattr__
只有该属性不存在时才会起作用。
因此,在支持__getattribute__
的Python版本,调用__getattr__
前必定会调用 __getattribute__
。__getattribute__
同样要避免"无限递归"的错误。
需要提醒的是,最好不要尝试去实现__getattribute__
,因为很少见到这种做法,而且很容易出bug。
示例代码及解析参见博客描述符https://www.cnblogs.com/yunlong-study/p/14338417.html
![](https://img2020.cnblogs.com/blog/1373456/202102/1373456-20210201062555618-1333194450.png)
一元操作符
__neg__(self) 定义正号的行为:+x
__pos__(self) 定义符号的行为:-x
__abs__(self) 定义当被abs()调用时的行为
__invert__(self) 定义按位取反的行为:~x
__neg__(self) 实现取负操作,例如 -some_object。
__round__(self, n) 实现内建函数 round() ,n 是近似小数点的位数。
__floor__(self) 实现 math.floor() 函数,即向下取整。
__ceil__(self) 实现 math.ceil() 函数,即向上取整。
__iadd__(self, other) | 定义赋值加法的行为:+= |
__isub__(self, other) | 定义赋值减法的行为:-= |
__imul__(self, other) | 定义赋值乘法的行为:*= |
__itruediv__(self, other) | 定义赋值真除法的行为:/= |
__ifloordiv__(self, other) | 定义赋值整数除法的行为://= |
__imod__(self, other) | 定义赋值取模算法的行为:%= |
__ipow__(self, other[, modulo]) | 定义赋值幂运算的行为:**= |
__ilshift__(self, other) | 定义赋值按位左移位的行为:<<= |
__irshift__(self, other) | 定义赋值按位右移位的行为:>>= |
__iand__(self, other) | 定义赋值按位与操作的行为:&= |
__ixor__(self, other) | 定义赋值按位异或操作的行为:^= |
__ior__(self, other) | 定义赋值按位或操作的行为:|= |
反算术运算符
这里只需要解释一下概念即可。
假设针对some_object这个对象:
some_object + other
上面的代码非常正常地实现了some_object的__add__
方法。那么如果遇到相反的情况呢?
other + some_object
这时候,如果other没有定义__add__
方法,但是some_object定义了__radd__
, 那么上面的代码照样可以运行。
这里的__radd__(self, other)
就是__add__(self, other)
的反算术运算符。
所以,类比的,我们就知道了更多的反算术运算符, 就不一一展开了:
__rsub__(self, other)
__rmul__(self, other)
__rmul__(self, other)
__rfloordiv__(self, other)
__rdiv__(self, other)
__rtruediv__(self, other)
__rmod__(self, other)
__rdivmod__(self, other)
__rpow__(self, other)
__rlshift__(self, other)
__rrshift__(self, other)
__rand__(self, other)
__ror__(self, other)
__rxor__(self, other
__cmp__(self, other)
如果该方法返回负数,说明self < other
; 返回正数,说明self > other
; 返回0说明self == other
。
强烈不推荐来定义__cmp__
, 取而代之, 最好分别定义__lt__
等方法从而实现比较功能。__cmp__
在Python3中被废弃了。
__eq__(self, other)
定义了比较操作符==
的行为.
__ne__(self, other)
定义了比较操作符!=
的行为.
__lt__(self, other)
定义了比较操作符<
的行为.
__gt__(self, other)
定义了比较操作符>
的行为.
__le__(self, other)
定义了比较操作符<=
的行为.
__ge__(self, other)
定义了比较操作符>=
的行为.
__lt__(self, other) | 定义小于号的行为:x < y 调用 x.__lt__(y) |
__le__(self, other) | 定义小于等于号的行为:x <= y 调用 x.__le__(y) |
__eq__(self, other) | 定义等于号的行为:x == y 调用 x.__eq__(y) |
__ne__(self, other) | 定义不等号的行为:x != y 调用 x.__ne__(y) |
__gt__(self, other) | 定义大于号的行为:x > y 调用 x.__gt__(y) |
__ge__(self, other) | 定义大于等于号的行为:x >= y 调用 x.__ge__(y) |
15.上下文管理
__enter__(self)
__enter__
会返回一个值,并赋值给as
关键词之后的变量。在这里,你可以定义代码段开始的一些操作。
__exit__(self, exception_type, exception_value, traceback)
__exit__
定义了代码段结束后的一些操作,可以这里执行一些清除操作,或者做一些代码段结束后需要立即执行的命令,比如文件的关闭,socket断开等。
如果代码段成功结束,那么exception_type, exception_value, traceback 三个参数传进来时都将为None。
如果代码段抛出异常,那么传进来的三个参数将分别为: 异常的类型,异常的值,异常的追踪栈。
如果__exit__
返回True, 那么with声明下的代码段的一切异常将会被屏蔽。
如果__exit__
返回None, 那么如果有异常,异常将正常抛出,这时候with的作用将不会显现出来。
with open('foo.txt') as bar:
# do something with bar
在with声明的代码段中,我们可以做一些对象的开始操作和清除操作,还能对异常进行处理。
这需要实现两个魔术方法: __enter__
和 __exit__
。
class TmpTest: def __init__(self,filename): self.filename=filename def __enter__(self): self.f = open(self.filename, 'r') return self.f def __exit__(self, exc_type, exc_val, exc_tb): self.f.close() test=TmpTest('file') with test as t: print ('test result: {}'.format(t)) """ 结果: test result: <_io.TextIOWrapper name='file' mode='r' encoding='cp936'> """
如果在__init__或者__enter__中抛出异常,则不会进入到__exit__中。如果在__exit__中返回True,则不会产生异常。
16.构造自定义容器(Container)
在Python中,常见的容器类型有: dict, tuple, list, string。
其中tuple, string是不可变容器,dict, list是可变容器。
可变容器和不可变容器的区别在于,不可变容器一旦赋值后,不可对其中的某个元素进行修改。
比如定义了l = [1, 2, 3]
和t = (1, 2, 3)
后, 执行l[0] = 0
是可以的,但执行t[0] = 0
则会报错。
如果我们要自定义一些数据结构,使之能够跟以上的容器类型表现一样,那就需要去实现某些协议。
这里的协议跟其他语言中所谓的"接口"概念很像,一样的需要你去实现才行,只不过没那么正式而已。
如果要自定义不可变容器类型,只需要定义__len__
和 __getitem__
方法;
如果要自定义可变容器类型,还需要在不可变容器类型的基础上增加定义__setitem__
和 __delitem__
。
如果你希望你的自定义数据结构还支持"可迭代", 那就还需要定义__iter__
。
__len__(self)
需要返回数值类型,以表示容器的长度。该方法在可变容器和不可变容器中必须实现。
__getitem__(self, key)
当你执行self[key]
的时候,调用的就是该方法。该方法在可变容器和不可变容器中也都必须实现。
调用的时候,如果key的类型错误,该方法应该抛出TypeError;
如果没法返回key对应的数值时,该方法应该抛出ValueError。
__setitem__(self, key, value)
当你执行self[key] = value
时,调用的是该方法。
__delitem__(self, key)
当你执行del self[key]
的时候,调用的是该方法。
__iter__(self)
该方法需要返回一个迭代器(iterator)。当你执行for x in container:
或者使用iter(container)
时,该方法被调用。
__reversed__(self)
如果想要该数据结构被內建函数reversed()
支持,就还需要实现该方法。
__contains__(self, item)
如果定义了该方法,那么在执行item in container
或者 item not in container
时该方法就会被调用。
如果没有定义,那么Python会迭代容器中的元素来一个一个比较,从而决定返回True或者False。
__missing__(self, key)
dict
字典类型会有该方法,它定义了key如果在容器中找不到时触发的行为。
比如d = {'a': 1}
, 当你执行d[notexist]
时,d.__missing__('notexist')
就会被调用。
下面举例,使用上面讲的魔术方法来实现Haskell语言中的一个数据结构。
# -*- coding: utf-8 -*- classFunctionalList: ''' 实现了内置类型list的功能,并丰富了一些其他方法: head, tail, init, last, drop, 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): 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 FunctionalList(reversed(self.values)) defappend(self, value): self.values.append(value) defhead(self): # 获取第一个元素 return self.values[0] deftail(self): # 获取第一个元素之后的所有元素 return self.values[1:] definit(self): # 获取最后一个元素之前的所有元素 return self.values[:-1] deflast(self): # 获取最后一个元素 return self.values[-1] defdrop(self, n): # 获取所有元素,除了前N个 return self.values[n:] deftake(self, n): # 获取前N个元素 return self.values[:n]
我们再举个例子,实现Perl语言的AutoVivification,它会在你每次引用一个值未定义的属性时为你自动创建数组或者字典。
classAutoVivification(dict): """Implementation of perl's autovivification feature.""" def__missing__(self, key): value = self[key] = type(self)() return value weather = AutoVivification() weather['china']['guangdong']['shenzhen'] = 'sunny' weather['china']['hubei']['wuhan'] = 'windy' weather['USA']['California']['Los Angeles'] = 'sunny' print weather # 结果输出:{'china': {'hubei': {'wuhan': 'windy'}, 'guangdong': {'shenzhen': 'sunny'}}, 'USA': {'California': {'Los Angeles': 'sunny'}}}
在Python中,关于自定义容器的实现还有更多实用的例子,但只有很少一部分能够集成在Python标准库中,比如
class FunctionalList: '''一个列表的封装类,实现了一些额外的函数式 方法,例如head, tail, init, last, drop和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): # 如果键的类型或值不合法,列表会返回异常 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.valuse[1:] def init(self): # 取得除最后一个元素外的所有元素 return self.values[:-1] def last(self): # 取得最后一个元素 return self.values[-1] def drop(self, n): # 取得除前n个元素外的所有元素 return self.values[n:] def take(self, n): # 取得前n个元素 return self.values[:n]
17.描述符
__get__
, __set__
, __delete__
。请看博客描述符https://www.cnblogs.com/yunlong-study/p/14338417.html
18.__format__(self, formatstr)
"Hello, {0:abc}".format(a)
等价于format(a, "abc")
, 等价于a.__format__("abc")
。
这在需要格式化展示对象的时候非常有用,比如格式化时间对象。
19.__instancecheck__/__subclasscheck__
-
__instancecheck__(self, instance)
检查一个实例是否是你定义的类的一个实例(例如 isinstance(instance, class) )。
-
__subclasscheck__(self, subclass)
检查一个类是否是你定义的类的子类(例如 issubclass(subclass, class) )。
20.拷贝
-
__copy__(self) 对实例使用
copy.copy()
时调用。返回"浅复制"的对象。定义对类的实例使用 copy.copy() 时的行为。 copy.copy() 返回一个对象的浅拷贝,这意味着拷贝出的实例是全新的,然而里面的数据全都是引用的。也就是说,对象本身是拷贝的,但是它的数据还是引用的(所以浅拷贝中的数据更改会影响原对象)。
-
__deepcopy__(self, memodict=) 对实例使用
copy.deepcopy()
时调用。返回"深复制"的对象。定义对类的实例使用 copy.deepcopy() 时的行为。 copy.deepcopy() 返回一个对象的深拷贝,这个对象和它的数据全都被拷贝了一份。 memodict 是一个先前拷贝对象的缓存,它优化了拷贝过程,而且可以防止拷贝递归数据结构时产生无限递归。当你想深拷贝一个单独的属性时,在那个属性上调用 copy.deepcopy() ,使用 memodict 作为第一个参数。
21.pickle(用到详细看)
-
__getinitargs__(self)
如果你想让你的类在反pickle时调用 __init__ ,你可以定义 __getinitargs__(self) ,它会返回一个参数元组,这个元组会传递给 __init__ 。注意,这个方法只能用于旧式类。
-
__getnewargs__(self)
对新式类来说,你可以通过这个方法改变类在反pickle时传递给 __new__ 的参数。这个方法应该返回一个参数元组。
-
__getstate__(self)
你可以自定义对象被pickle时被存储的状态,而不使用对象的 __dict__ 属性。 这个状态在对象被反pickle时会被 __setstate__ 使用。
-
__setstate__(self)
当一个对象被反pickle时,如果定义了 __setstate__ ,对象的状态会传递给这个魔法方法,而不是直接应用到对象的 __dict__ 属性。这个魔法方法和 __getstate__ 相互依存:当这两个方法都被定义时,你可以在Pickle时使用任何方法保存对象的任何状态。
-
__reduce__(self)
当定义扩展类型时(也就是使用Python的C语言API实现的类型),如果你想pickle它们,你必须告诉Python如何pickle它们。 __reduce__ 被定义之后,当对象被Pickle时就会被调用。它要么返回一个代表全局名称的字符串,Pyhton会查找它并pickle,要么返回一个元组。这个元组包含2到5个元素,其中包括:一个可调用的对象,用于重建对象时调用;一个参数元素,供那个可调用对象使用;被传递给 __setstate__ 的状态(可选);一个产生被pickle的列表元素的迭代器(可选);一个产生被pickle的字典元素的迭代器(可选);
-
__reduce_ex__(self)
__reduce_ex__ 的存在是为了兼容性。如果它被定义,在pickle时 __reduce_ex__ 会代替 __reduce__ 被调用。 __reduce__ 也可以被定义,用于不支持 __reduce_ex__ 的旧版pickle的API调用。
我们的例子是 Slate ,它会记住它的值曾经是什么,以及那些值是什么时候赋给它的。然而 每次被pickle时它都会变成空白,因为当前的值不会被存储:
import time class Slate: '''存储一个字符串和一个变更日志的类 每次被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_change(self): print 'Changelog for Slate object:' for k,v in self.history.items(): print '%s\t %s' % (k,v) def __getstate__(self): # 故意不返回self.value或self.last_change # 我们想在反pickle时得到一个空白的slate return self.history def __setstate__(self): # 使self.history = slate,last_change # 和value为未定义 self.history = state self.value, self.last_change = None, None
22.其他
__hash__(self)
对实例使用hash()
时调用, 返回值是数值类型。
__nonzero__(self)
对实例使用bool()
时调用, 返回True或者False。
你可能会问, 为什么不是命名为__bool__
? 我也不知道。
我只知道该方法在Python3中改名为__bool__
了。
__dir__(self)
对实例使用dir()
时调用。通常实现该方法是没必要的。
__sizeof__(self)
对实例使用sys.getsizeof()
时调用。返回对象的大小,单位是bytes。
__metaclass__
定义当前类的元类
- __doc__
获取文档信息——就是写在类最前面的注释
从多篇文章加已整合。应该是蛮全的。不正确的地方,欢迎指出。