Python笔记 · 魔法函数 / Magic Methods / Dunder Methods

2023-02-23 20:58:12

在上篇文章《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), 提供给你一个自定义这些类的实例化过程的途径。

  1. 当我们要继承一些不可变类时,__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)本身的固有特性。

  1. 通过__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/

  • 作者:bluishglc
  • 原文链接:https://blog.csdn.net/bluishglc/article/details/128188200
    更新时间:2023-02-23 20:58:12