在上篇文章《Python笔记 · 私有方法、私有属性 & 单下划线、双下划线》我们介绍过以前置双下划线开始,后置双下划线结束的方法名:__*__
,这是系统定义的一批特殊函数,通常被称之为:魔法函数(Magic Methods 或 Dunder Methods),其中Dunder意为:“Double Under (Underscores)”。本文我们就对这些Dunder函数做一次系统介绍。
魔法函数并不会被直接调用,而在类的某些特殊行为发生时被自动调用。实际上,从其他语言观察Python的这些魔法函数,都有对应的解决方案,只是Python选择了这种“内置的预定义的函数”作为Python的处理方式。我个人倾向于把这些魔法函数分为:
- 对象生命周期相关的操作
- 对象基本操作(属性,toString等)
- 运算符重载
- 集合相关操作
- with上下文管理器
- 协程相关
(注:上图引用自:https://zhuanlan.zhihu.com/p/344951719)
以下是各魔法方法的列表:
方法名 | 用途 |
---|---|
__init__ | Initialise object |
__new__ | Create object |
__del__ | Destroy object |
__repr__ | Compute “official” string representation / repr(obj) |
__str__ | Pretty print object / str(obj) / print(obj) |
__bytes__ | bytes(obj) |
__format__ | Custom string formatting |
__lt__ | obj < … |
__le__ | obj <= … |
__eq__ | obj == … |
__ne__ | obj != … |
__gt__ | obj > … |
__ge__ | obj >= … |
__hash__ | hash(obj) / object as dictionary key |
__bool__ | bool(obj) / define Truthy/Falsy value of object |
__getattr__ | Fallback for attribute access |
__getattribute__ | Implement attribute access: obj.name |
__setattr__ | Set attribute values: obj.name = value |
__delattr__ | Delete attribute: del obj.name |
__dir__ | dir(obj) |
__get__ | Attribute access in descriptor |
__set__ | Set attribute in descriptor |
__delete__ | Attribute deletion in descriptor |
__init_subclass__ | Initialise subclass |
__set_name__ | Owner class assignment callback |
__instancecheck__ | isinstance(obj, …) |
__subclasscheck__ | issubclass(obj, …) |
__class_getitem__ | Emulate generic types |
__call__ | Emulate callables / obj(*args, **kwargs) |
__len__ | len(obj) |
__length_hint__ | Estimate length for optimisation purposes |
__getitem__ | Access obj[key] |
__setitem__ | obj[key] = … or `obj[] |
__delitem__ | del obj[key] |
__missing__ | Handle missing keys in dict subclasses |
__iter__ | iter(obj) / for … in obj (iterating over) |
__reversed__ | reverse(obj) |
__contains__ | … in obj (membership test) |
__add__ | obj + … |
__radd__ | … + obj |
__iadd__ | obj += … |
__sub__ | obj - … |
__mul__ | obj * … |
__matmul__ | obj @ … |
__truediv__ | obj / … |
__floordiv__ | obj // … |
__mod__ | obj % … |
__divmod__ | divmod(obj, …) |
__pow__ | obj ** … |
__lshift__ | obj << … |
__rshift__ | obj >> … |
__and__ | obj & … |
__xor__ | obj ^ … |
__or__ | obj |
__neg__ | -obj (unary) |
__pos__ | +obj (unary) |
__abs__ | abs(obj) |
__invert__ | ~obj (unary) |
__complex__ | complex(obj) |
__int__ | int(obj) |
__float__ | float(obj) |
__index__ | Losslessly convert to integer |
__round__ | round(obj) |
__trunc__ | math.trunc(obj) |
__floor__ | math.floor(obj) |
__ceil__ | math.ceil(obj) |
__enter__ | with obj (enter context manager) |
__exit__ | with obj (exit context manager) |
__await__ | Implement awaitable objects |
__aiter__ | aiter(obj) |
__anext__ | anext(obj) |
__aenter__ | async with obj (enter async context manager) |
__aexit__ | async with obj (exit async context manager) |
(注:更多关联到官方文档的解释,请参考:https://mathspp.com/blog/pydonts/dunder-methods#fn:2)
由于方法众多,我们可以使用下述方法来查看当前对象所有的函数,所有的魔法函数也会罗列出来。
dir(<your-object>)
由于魔法函数众多,我们不会在本文中一一解读,以后在使到的场景中再跟进做一些笔记。本文,我们只关注一部分最基本也最常见的魔法函数。
__init__()
总是第一个被提起的魔法函数,实际上就是Python的构造函数。该函数会在对象初始化的时由解释器自动调用,程序员可以自由选择实现或不实现,但就像其他编程语言中的构造函数一样,一般都会选择实现。如果不实现,则对象属性是不会被声明和初始化的,虽然Python是动态语言,在后续使用对象的过程中依然可以随时添加和初始化属性,但是从编程习惯上讲,还是在构造函数中显示声明出所有属性是更好的选择。
__new__()
在object类中存在一个静态的__new__(cls, *args, **kwargs)
方法,该方法需要传递一个参数 cls,cls 表示需要实例化的类,此参数在实例化时由 Python 解释器自动提供,__new__
方法必须有返回值,且返回的是被实例化的实例,只有在该实例返回后才会调用__init__
来进行初始化,初始化所用的实例就是__new__
返回的结果,也就可以认为是 self。
__init__
和__new__
的区别
__new__
的调用发生在__init__
之前__new__
是类级别的方法(方法参数是cls),__init__
是实例级别的方法(方法参数是self)
当我们创建一个Python实例时,Python会先调用__new__
来创建一个实例,然后再调用这个实例的__init__
方法来完成一些实例属性的初始化操作。
绝大多数情况下, 我们只需要实现__init__
方法,关于__new__
有两种典型的应用场景:
当你继承一些不可变的class时(比如int, str, tuple), 提供给你一个自定义这些类的实例化过程的途径。
- 当我们要继承一些不可变类时,
__new__
给了我们一个“调整”这些类的实例化细节的入口,例如如下代码:
class PositiveInteger(int):
def __new__(cls, value):
return super(PositiveInteger, cls).__new__(cls, abs(value))
i = PositiveInteger(-3)
print(i)
由于Int是不可变类,继承自它的PositiveInteger会自动取绝对值作为其值。尽管该意图在init方法中同样可以实现,但是在类级别的new方法中实现是更合理的,应为是类型(即PositiveInteger)本身的固有特性。
- 通过
__new__
实现单例
class Single(object):
_instance = None
def __new__(cls, *args, **kw):
if cls._instance is None:
cls._instance = object.__new__(cls, *args, **kw)
return cls._instance
def __init__(self):
pass
single1 = Single()
single2 = Single()
single1.attr1='value1'
print(single1.attr1)
print(single2.attr1)
print(id(single1) == id(single2))
程序输出:
value1
value1
True
基于new实现的单例能很好地体现new区别于init的地方。
__iter__()
只要定义了__iter__
() 方法对象,就是可迭代对象;这意味着,我们可以迭代我们自己定义的对象。
__next__()
函数next() 是 iterator 区别于 iterable 的关键,它允许我们显式地获取一个元素。当调用 next() 方法时,实际上产生了 2 个操作:一是更新 iterator 状态,令其指向后一项,以便下一次调用,二是返回当前结果。
__call__()
对象通过提供__call__
() 方法可以模拟函数的行为,如果一个对象提供了该方法,就可以像函数一样使用它。
__len__()
len 调用后会调用对象的__len__
函数,我们可以为其定制输出。
__str__()
直接打印对象的实现方法,str 是被 print 函数调用的,一般都是 return 一个什么东西,这个东西应该是以字符串的形式表现的。如果不是要用 str() 函数转换,我们可以直接 print 的对象都是实现了__str__
这个方法的,比如 dict。
__repr__()
函数 str() 用于将值转化为适于人阅读的形式,而 repr() 转化为供解释器读取的形式,某对象如果没有适于人阅读的解释形式的话,我们可以定制__repr__
的输出。
__setitem__()
该函数可以给对象赋值,我们可以以下标的方式对其进行操作
__getitem__()
与上函数相反,getitem 可以使对象支持以下标的方式获取值。
__delitem__()
该函数支持以下标方式删除对象数据,实现了这三个函数,这个类就像字典一样,具备了基本的增删查功能,有时候这样写会很方便。
__del__()
这可以说是一个析构器,或者回收器,在对象引用数降到0时执行,有时可能还需要等一会再执行,所以一般不推荐使用,但是在代码中我们偶尔可以用它来实现一些必须要做的,但是并不紧急的事。
__setattr__()
该函数可以设置函数的属性。
__getattr__()
获取对象属性,只有在属性没有找到的时候调用。
__getattribute__()
该函数和上面介绍的__getattr__
很像,都是获取属性,但是__getattr__
是在属性不存在时被调用,而__getattribute__
是无条件被调用,这样会方便我们做一些控制,需要注意,一旦定义 了__getattribute__
,则__getattr__
不再会被调用,除非显式调用。
##__delattr__()
本函数的作用是删除属性,实现了该函数的类可以用 del 命令来删除属性。
参考:
https://www.cnblogs.com/chenhuabin/p/13752770.html
https://levelup.gitconnected.com/python-dunder-methods-ea98ceabad15
https://www.tutorialsteacher.com/python/magic-methods-in-python
https://mathspp.com/blog/pydonts/dunder-methods
https://lupanlpb.github.io/2019/08/14/Python%E5%B8%B8%E7%94%A8%E9%AD%94%E6%B3%95%E5%87%BD%E6%95%B0/