原创
最近更新: 2023/03/03 21:40

Python的魔法属性和魔术方法

Python魔术方法大全 - 码农高天

魔法属性

魔法属性涉及了类和对象的一些基本信息。

class Father(object):
    pass

class Magic(Father):
    '''doc 的内容'''
    a = 1
    def __init__(self):
        self.b = 2

# 下面的魔法属性,如果没有额外说明,则通过类名访问和通过实例访问结果相同。
# 应当注意的是,python中的类本身也是type类的对象。
# 类名
print(Magic.__class__)		# <class 'type'> # 这里是class的class
print(Magic().__class__)	# <class '__main__.Magic'>
# 模块名
print(Magic.__module__ )	# __main__
print(Magic.__name__)		# Magic # 只能用类名访问
print(__name__)				# __main__ # 常用于判断是否是导入的文件
# 实例属性列表,不会打印类的属性
print(Magic.__dict__)		# {'a': 1, '__init__': ...} (后面省略)
print(Magic().__dict__)		# {'b': 2}
# 父类的继承顺序,当访问一个属性时,在继承链条上的查询顺序,只能用类名访问。
# 这个顺序的算法比较复杂,见 https://www.bilibili.com/video/BV1V5411S7dY
print(Magic.__mro__)		# (<class '__main__.Magic'>, <class '__main__.Father'>, <class 'object'>)
# 说明文档
print(Magic.__doc__) 		# doc 的内容
# 列出所有子类,只能通过类名访问。
# 访问object的子类会打印一大堆东西。
print(Father.__subclasses__())	# [<class '__main__.Magic'>]

类和对象操作

Python常用魔术方法

魔术方法的官方称谓是 special method,又因为其名字被双下划线包裹的特点,被称为 dunder(double underscore) method,用于对类进行客制化操作。

创建和销毁

主要是__new__(), __init__(), __del__()三个方法,其中__init__()方法最常用。

  • __new__方法是静态的,因为它是构造函数,调用该函数时,类对象尚未创建,所以其传入一个cls作为参数。返回构造完成的对象。
  • __init__方法用于初始化,需要传入对象本身self作为参数。在__new__方法调用成功之后调用。
  • __del__方法当对象被销毁(对象的引用是0,或被垃圾回收)时调用。

关于__new__方法,进一步的讨论见 Python MetaClass元类详解

class Magic(object):
    def __new__(cls, *args, **kwargs):
        print("__new__:", cls, args, kwargs) # cls对象打印的是实例.__class__的结果
        ret = super().__new__(cls) # 调用父类的__new__()方法创建对象
        # 在此处进行修改可以控制返回的对象
        return ret

    def __init__(self, attr):
        super().__init__()  # 调用父类的构造函数来对父类成员进行初始化
        print("__init__:", self, attr)

    def __del__(self):
        print("__del__")

m = Magic("magic");

'''
# 对象在被创建时,参数会同时传入__new__和__init__方法中
__new__: <class '__main__.Magic'> ('magic',) {}
__init__: <__main__.Magic object at 0x0000022BB03EFBB0> magic
__del__
'''

使用__new__方法实现单例模式(Singleton)

class Singleton(object):
    def __new__(cls, *args, **kwargs):
        if not hasattr(cls, '_instance'):
            cls._instance = super().__new__(cls, *args, **kwargs)
        return cls._instance

a = Singleton()
b = Singleton()
a == b # True

__init_subclass__(cls, ...)会在子类被创建时调用。

class Base:
    def __init_subclass__(cls, name):
        cls.x = 1
        cls.name = name

class A(Base, name="sub"):
    pass

print(A.x)		#1
print(A.name)	#sub

__call__()

在对象中定义了__call__()方法之后,该对象就成为一个 callable,等价于一个函数。事实上,一切可调用的对象,都实现了__call__()方法。

class Magic():
    def __call__(self, *args, **kwargs):
        print(self, args, kwargs)

m = Magic();
m("arg", args1="abc") # 等价于 m.__call__("arg", args1="abc")

# <__main__.Magic object at 0x00000142A7537C40> ('arg',) {'args1': 'abc'}

__hash__()

返回一个整数。用于通过hash(obj)方法计算对象的哈希值,在集合和字典数据结构中用于作为 key 的计算值。

推荐方法:在重载过程中调用 Python 内建的hash()函数

python中,集合在添加新元素时,会先计算hash值,如果没有冲突,则直接添加,如果出现冲突,则判断两个对象是否相等(__eq__),如果相等,则丢弃新元素。

__eq____hash__方法在object类中都有默认的,基于对象内存地址的实现。如果只重写__eq__方法,那么__hash__方法会被清除,对象会变成 unhashable,此时对象也无法添加入集合或字典。所以,重写__eq__方法后,一般也要重写__hash__方法。

class Magic(object):
	def __init__(self, a):
		self.a = a
    def __hash__(self):
        return 100
    def __eq__(self, other):
        return True

s = set()
s.add(Magic(1))
s.add(Magic(2))
for i in s:
    print(i.a) # 1

属性操作

主要是__getattr__, __getattribute__, __setattr__, __delattr__四个方法。当使用点方法进行属性访问时调用。

class Magic():
    # 访问不存在的类属性时调用
    def __getattr__(self, name):
        print("Magic has no", name)
        return None # 如果没有返回值,默认返回None
    # 属性拦截器,使用点方法访问任意属性时调用。注意:此处对拦截一切属性都生效,包括各种内建属性、类方法等
    def __getattribute__(self, name):
        print("seek Magic for", name)
        # 此处不能写return self.name,会无限递归
		return super().__getattribute__(self, name)
        # return object.__getattribute__(self, name)
    def __setattr__(self, name, value):
        print("Magic." + name, " = ", value)
        # 此处不能写 self.name = value,会无限递归
        object.__setattr__(self, name, value)
	# 使用 del 关键词删除属性时调用
    def __delattr__(self, name):
        print("del Magic." + name)
		super().__delattr__(name)

m = Magic();
m.a = 123
print(m.a)
print(m.b)

'''
Magic.a  =  123
seek Magic for a
123
seek Magic for b
Magic has no b
None
'''

此外还有与描述器有关的__get__()__set__()__delete__()方法,此处不再展开。相关内容请参考:Python 黑魔法:描述器(descriptor)

其他常用方法

__str__(self) -> string # str(实例)或print(实例)等需要转字符串时调用,返回一个字符串。__repr__与__str__都被定义时会优先调用此方法。
__repr__(self) -> string # 与__str__方法作用相似,是__str__未定义时的默认输出,或通过repr(实例)调用。打印容器时会优先调用此方法。
__bool__(self) -> bool # 返回一个布尔值,当处于布尔表达式中时调用。
__format__(self) -> string # 在format(实例)或f-string时调用。
__bytes__(self) -> bytes # bytes(实例)时调用。
__dir__(self) -> list # dir(实例)时调用,返回所有属性列表,一般不需要重写。注意与__dict__区别,__dict__仅包含实例属性。

运算符重载

比较运算

重载比较运算符,建议返回一个布尔值

__lt__(self, other)    self < other
__le__(self, other)    self <= other
__eq__(self, other)    self == other
__ne__(self, other)    self != other
__gt__(self, other)    self > other
__ge__(self, other)    self >= other

__eq__如果未被定义,其默认实现是is操作。

如果定义了__eq__却没定义__ne__,计算!=时会调用__eq__然后取反。相应的,小于和大于,小于等于和大于等于也是一对。

双目算数运算

重载比较运算符,返回一个自定义结果。

__add__(self, other)           self + other
__sub__(self, other)           self - other
__mul__(self, other)           self * other
__matmul__(self, other)        self @ other
__truediv__(self, other)       self / other
__floordiv__(self, other)      self // other
__mod__(self, other)           self % other
__divmod__(self, other)        divmod(self, other)
__pow__(self, other[, modulo]) power(self, other[, mod]) 或 self ** other [% mod]
__lshift__(self, other)        self << other
__rshift__(self, other)        self >> other
__and__(self, other)           self & other
__xor__(self, other)           self ^ other
__or__(self, other)            self | other

# 上述方法有对应的r版本。当运算符左边的运算数未定义相关运算时,会调用右边对象的r版本运算。
# 此时会将右边的对象传入self参数。实现方法与原版基本一致。
__rmul__(self, other)          other * self

此处可以用isinstance(other, type)来对 other 进行讨论,实现对不同数据类型的运算。

赋值运算

双目运算符与赋值操作的结合。直接在左边操作数上面进行修改,一般是返回self。

__iadd__(self, other)             self += other
__isub__(self, other)             self -= other
__imul__(self, other)             self *= other
__itruediv__(self, other)         self /= other
__ifloordiv__(self, other)        self //= other
__imod__(self, other)             self %= other
__ipow__(self, other[, modulo])   self **= other
__ilshift__(self, other)          self <<= other
__irshift__(self, other)          self >>= other
__iand__(self, other)             self &= other
__ixor__(self, other)             self ^= other
__ior__(self, other)              self |= other

单目运算符

__pos__(self)      +self
__neg__(self)      -self
__abs__(self)      abs(self)
__invert__(self)   ~self

其他常用的魔术方法

显式类型转换

需要被显式转换为其他类型时才会调用,隐式转换不调用。返回值必须是函数名对应的类型。

__str__(self)           str(self)
__complex__(self)       complex(self)
__int__(self)           int(self)
__float__(self)         float(self)

取整函数

__round__(self[, n])    round(self)
__trunc__(self)         trunc(self) # math库中的函数
__floor__(self)         floor(self) # math库中的函数
__ceil__(self)          ceil(self)  # math库中的函数

with语句上下文管理

class Magic():
    def __init__(self):
        self.a = "before the with"
    def __enter__(self):
        self.a = "in the with"
        return self # 此处返回值会绑定到as后面的变量上
    def __exit__(self, exctype, excvalue, traceback):
        self.a = "after the with"

with Magic() as m:
    print(m.a) # in the with
print(m.a) # after the with

容器相关方法

用于构造容器,如自定义的列表或者字典等。

__len__(self)                   len(self) # 如果没有定义__bool__方法,当对象进行逻辑运算时会调用__len__
__getitem__(self, key)          self[key]
__setitem__(self, key, value)   self[key] = value
__delitem__(self, key)          del self[key]
__reversed__(self)              reversed(self)
__contains__(self, item)        item in self

迭代器相关方法

用于构造一个迭代器,使得该类的对象可以放在for循环中遍历。主要用到__iter____next__两个方法。

# 一个数字1到10的迭代器
class Magic(object):
    _max = 10
    def __iter__(self):
        # 此处进行迭代器的初始化
        self.a = 0
        return self # 必须return self,否则就构建不了迭代器
    def __next__(self):
        # 此处决定每次迭代生成的对象
        if self.a < self._max:
            self.a += 1
        else:
            # 设置终止迭代的条件
            raise StopIteration
        return self.a

for i in Magic():
    print(i, end=' ') # 1 2 3 4 5 6 7 8 9 10

评论区