Python 中的魔法函数,也被称为特殊方法或双下划线方法,是 Python 中一些特殊命名的函数,它们以双下划线开头和结尾。这些函数定义了对象在特定情况下的行为,例如创建、比较、运算、迭代等。
魔法函数主要是为某些特殊需求而设计的。例如__str__() 和__repr__() 函数用于打印输出对象的信息,__add__() 函数用于定义两个对象相加的行为,__len__() 函数定义当被 len() 调用时的行为等。Python 魔法函数是实现 Python 语法糖的一种方式,提高代码的可读性和可维护性,比如对象相加,大家更习惯 c = a + b 的形式,如果用户使用 a + b 能自动调用 a.__add__(b) 的话,自然要方便很多了,而魔法函数正好有这种功能。
魔法函数
__new__
__new__ 是类静态方法,
用于在创建新实例之前控制对象的创建。它是构造器方法__init__
之前被调用的。__new__
方法通常用于控制实例的创建,可以返回实例本身或者一个完全不同的对象。
比如下面使用 __new__ 实现 Singleton 单例。
class PointClass:
instance = None
def __new__(cls, *args, **kwargs):
print("__new__")
if cls.instance is None:
cls.instance = super().__new__(cls)
return cls.instance
def __init__(self, x, y):
print("__init__")
self.x = x
self.y = y
def __str__(self):
print("__str__")
return f"({self.x}, {self.y})"
p = PointClass(3.4, 4.6)
print(p)
# __new__
# __init__
# __str__
# (3.4, 4.6)
__init__
__init__ 对象初始化函数,在创建实例化对象时自动调用。这个最熟悉的就不多说了。
class TestClass:
def __init__(self, name):
self.name = name
obj = TestClass("Alice")
__str__
以用户友好的方式返回对象的字符串表示,使用 str() 时自动调用。
class PointClass:
def __init__(self, x, y):
self.x = x
self.y = y
def __str__(self):
return f"({self.x}, {self.y})"
p = PointClass(3, 4)
print(p) # (3, 4)
__repr__
以开发者友好的方式返回对象的字符串表示,使用 repr() 时自动调用。当使用 print() 函数打印对象时,首先调用的是 __str__ 方法,如果对象没有定义 __str__ 方法,才会调用 __repr__ 方法。
class PointClass:
def __init__(self, x, y):
self.x = x
self.y = y
def __str__(self):
return f"({self.x}, {self.y})"
def __repr__(self):
return f"Point({self.x}, {self.y})"
p = PointClass(3, 4)
print(p) # (3, 4)
print(str(p)) # (3, 4)
print(repr(p)) # Point(3, 4)
__len__
返回对象的长度,使用 len() 自动调用。
class ListClass:
def __init__(self, items):
self.items = items
def __len__(self):
return len(self.items)
my_list = ListClass([1, 2, 3, 4, 5])
print(len(my_list)) # 5
__missing__
当尝试访问字典中不存在的键时,如果定义了 __missing__
方法,它将被调用,而不是抛出 KeyError
。
class DictClass(dict):
def __missing__(self, key):
self[key] = "default"
return "default"
my_dict = DictClass({'a': 1, 'b': 2})
print(my_dict['c']) # default
print(my_dict.keys()) # dict_keys(['a', 'b', 'c'])
my_dict = dict({'a': 1, 'b': 2})
print(my_dict['c']) # KeyError: 'c'
__getitem__
获取对象的指定元素,当使用索引操作符[]
来访问对象的元素时自动调用。
class DictClass:
def __init__(self, items):
self.items = items
def __getitem__(self, key):
return self.items.get(key)
my_dict = DictClass({'a': 1, 'b': 2})
print(my_dict['a']) # 1
__setitem__
给对象的指定元素设置值,给对象设定值时自动调用。
class DictClass:
def __init__(self, items):
self.items = items
def __setitem__(self, key, value):
self.items[key] = value
my_dict = DictClass({'a': 1, 'b': 2})
my_dict['c'] = 3
print(my_dict.items) # {'a': 1, 'b': 2, 'c': 3}
__delitem__
删除对象指定元素,使用 del 删除对象元素时自动调用。
class DictClass:
def __init__(self, items):
self.items = items
def __delitem__(self, key):
del self.items[key]
my_dict = DictClass({'a': 1, 'b': 2})
del my_dict['a']
print(my_dict.items) # {'b': 2}
__contains__
判断对象是否包含指定元素,使用 in 判断时自动调用。
class ListClass:
def __init__(self, items):
self.items = items
def __contains__(self, item):
return item in self.items
my_list = ListClass([1, 2, 3, 4, 5])
print(3 in my_list) # True
__iter__
返回迭代器对象。
class MyList:
def __init__(self, items):
self.items = items
def __iter__(self):
# return iter(self.items)
return (item for item in self.items)
my_list = MyList([1, 2, 3, 4, 5])
for item in my_list:
print(item) # 依次输出: 1, 2, 3, 4, 5
__next__
返回迭代器的下一个元素,循环遍历获取对象元素时自动调用。
class ListClass:
def __init__(self, items):
self.items = items
self.index = 0
def __iter__(self):
return self
def __next__(self):
if self.index >= len(self.items):
raise StopIteration
value = self.items[self.index]
self.index += 1
return value
my_list = ListClass([1, 2, 3, 4, 5])
for item in my_list:
print(item) # 依次输出: 1, 2, 3, 4, 5
__eq__
判断两个对象是否相等,两个对象使用 == 判断时自动调用。
class PointClass:
def __init__(self, x, y):
self.x = x
self.y = y
def __eq__(self, other):
return self.x == other.x and self.y == other.y
p1 = PointClass(3, 4)
p2 = PointClass(3, 4)
p3 = PointClass(5, 6)
print(p1 == p2) # True
print(p1 == p3) # False
__ne__
判断两个对象是否不等,两个对象使用 != 判断时自动调用。
class PointClass:
def __init__(self, x, y):
self.x = x
self.y = y
def __str__(self):
return f"({self.x}, {self.y})"
def __ne__(self, other):
if self.x == other.x and self.y == other.y:
return False
return True
p1 = PointClass(3, 0)
p2 = PointClass(3, 4)
print(p1 != p1) # False
print(p1 != p2) # True
__abs__
输出对象的绝对值。
class PointClass:
def __init__(self, x, y):
self.x = x
self.y = y
def __abs__(self):
return (self.x * self.x + self.y * self.y) ** 0.5
p1 = PointClass(3, 4)
print(abs(p1)) # 5.0
__lt__
判断一个对象是否小于另一个对象。
class PointClass:
def __init__(self, x, y):
self.x = x
self.y = y
def __lt__(self, other):
return self.x < other.x and self.y < other.y
p1 = PointClass(3, 4)
p2 = PointClass(3, 4)
p3 = PointClass(5, 6)
print(p1 < p2) # False
print(p1 < p3) # True
__le__
判断一个对象是否小于或等于另一个对象。
class PointClass:
def __init__(self, x, y):
self.x = x
self.y = y
def __le__(self, other):
return self.x <= other.x and self.y <= other.y
p1 = PointClass(3, 4)
p2 = PointClass(3, 4)
p3 = PointClass(5, 6)
print(p1 <= p2) # True
print(p1 <= p3) # True
__gt__
判断一个对象是否大于另一个对象。
class PointClass:
def __init__(self, x, y):
self.x = x
self.y = y
def __gt__(self, other):
return self.x > other.x and self.y > other.y
p1 = PointClass(3, 4)
p2 = PointClass(3, 4)
p3 = PointClass(1, 2)
print(p1 > p2) # False
print(p1 > p3) # True
__ge__
判断一个对象是否大于或等于另一个对象。
class PointClass:
def __init__(self, x, y):
self.x = x
self.y = y
def __ge__(self, other):
return self.x >= other.x and self.y >= other.y
p1 = PointClass(3, 4)
p2 = PointClass(3, 4)
p3 = PointClass(1, 2)
print(p1 >= p2) # True
print(p1 >= p3) # True
__neg__
__neg__
方法用于定义对象被取反时的行为。当使用负号操作符 - 对一个对象进行操作时,会查找并调用该对象的__neg__
方法。
class PointClass:
def __init__(self, x, y):
self.x = x
self.y = y
def __str__(self):
return f"({self.x}, {self.y})"
def __neg__(self):
return PointClass(-self.x, -self.y)
p = PointClass(3, 4)
print(-p) # (-3, -4)
__pos__
class PointClass:
def __init__(self, x, y):
self.x = x
self.y = y
def __str__(self):
return f"({self.x}, {self.y})"
def __pos__(self):
return PointClass(+self.x, +self.y)
p = PointClass(3, 4)
print(+p) # (3, 4)
__invert__
class PointClass:
def __init__(self, x, y):
self.x = x
self.y = y
def __str__(self):
return f"({self.x}, {self.y})"
def __invert__(self):
return PointClass(~self.x, ~self.y)
p = PointClass(3, 4)
print(~p) # (-4, -5)
__add__
定义对象的加法操作,对象之间使用 + 自动调用。
class PointClass:
def __init__(self, x, y):
self.x = x
self.y = y
def __add__(self, other):
return PointClass(self.x + other.x, self.y + other.y)
def __str__(self):
return f"({self.x}, {self.y})"
p1 = PointClass(1, 2)
p2 = PointClass(3, 4)
print(p1 + p2) # (4, 6)
__iadd__
定义对象的自加法操作,对象之间使用 += 自动调用。
class PointClass:
def __init__(self, x, y):
self.x = x
self.y = y
def __str__(self):
return f"({self.x}, {self.y})"
def __iadd__(self, other):
if isinstance(other, int):
return PointClass(self.x + other, self.y + other)
elif isinstance(other, PointClass):
return PointClass(self.x + other.x, self.y + other.y)
else:
raise TypeError(f"unsupported operand types: {type(other)}")
p = PointClass(3, 4)
p += 1
print(p) # (4, 5)
p += PointClass(1, 2)
print(p) # (5, 7)
p += "1"
print(p) # TypeError: unsupported operand types: <class 'str'>
__radd__
__radd__
用于实现反射加法操作。当你的对象作为加法操作符+
的右侧操作数时,如果左侧操作数的类型没有定义与你的对象相加的方法,Python会检查你的对象是否定义了__radd__
方法,如果定义了,就会调用它。
class PointClass:
def __init__(self, x, y):
self.x = x
self.y = y
def __radd__(self, other):
return PointClass(other + self.x, other + self.y)
def __str__(self):
return f"({self.x}, {self.y})"
p = PointClass(1, 2)
print(3 + p) # (4, 5)
print(p + 3) # TypeError: unsupported operand type(s) for +: 'PointClass' and 'int'
当然,针对可交换的加法, __radd__ 调用也可以直接委托 __add__。
class PointClass:
def __init__(self, x, y):
self.x = x
self.y = y
def __add__(self, other):
print("call __add__")
return PointClass(other + self.x, other + self.y)
def __radd__(self, other):
print("call __radd__")
return self + other
def __str__(self):
return f"({self.x}, {self.y})"
p = PointClass(1, 2)
print(3 + p)
# call __radd__
# call __add__
# (4, 5)
__sub__
定义对象的减法操作,对象之间使用 - 自动调用。
class PointClass:
def __init__(self, x, y):
self.x = x
self.y = y
def __sub__(self, other):
return PointClass(self.x - other.x, self.y - other.y)
def __str__(self):
return f"({self.x}, {self.y})"
p1 = PointClass(1, 2)
p2 = PointClass(3, 4)
print(p1 - p2) # (-2, -2)
__isub__
定义对象的自减法操作,对象之间使用 -= 自动调用。
class PointClass:
def __init__(self, x, y):
self.x = x
self.y = y
def __str__(self):
return f"({self.x}, {self.y})"
def __isub__(self, other):
if isinstance(other, int):
return PointClass(self.x - other, self.y - other)
elif isinstance(other, PointClass):
return PointClass(self.x - other.x, self.y - other.y)
else:
raise TypeError(f"unsupported operand types: {type(other)}")
p = PointClass(3, 4)
p -= 1
print(p) # (2, 3)
p -= PointClass(1, 2)
print(p) # (1, 1)
p -= "1"
print(p) # TypeError: unsupported operand types: <class 'str'>
__rsub__
class PointClass:
def __init__(self, x, y):
self.x = x
self.y = y
def __rsub__(self, other):
return PointClass(other - self.x, other - self.y)
def __str__(self):
return f"({self.x}, {self.y})"
p = PointClass(1, 2)
print(3 - p) # (2, 1)
__mul__
定义对象的乘法操作,对象之间使用 * 自动调用,可以根据操作数类型进行对象乘法和数乘。
class PointClass:
def __init__(self, x, y):
self.x = x
self.y = y
def __mul__(self, other):
if isinstance(other, PointClass):
return PointClass(self.x * other.x, self.y * other.y)
elif isinstance(other, int) or isinstance(other, float):
return PointClass(self.x * other, self.y * other)
else:
raise TypeError(f"Unsupported operand type: {type(other)}")
def __str__(self):
return f"({self.x}, {self.y})"
p1 = PointClass(1, 2)
p2 = PointClass(3, 4)
print(p1 * p2) # (3, 8)
print(p1 * 3) # (3, 6)
print(p1 * "t") # TypeError: Unsupported operand type: <class 'str'>
__imul__
定义对象的自乘法操作,对象之间使用 *= 自动调用。
class PointClass:
def __init__(self, x, y):
self.x = x
self.y = y
def __str__(self):
return f"({self.x}, {self.y})"
def __imul__(self, other):
if isinstance(other, int):
return PointClass(self.x * other, self.y * other)
else:
raise TypeError(f"unsupported operand types: {type(other)}")
p = PointClass(3, 4)
p *= 2
print(p) # (6, 8)
p *= "1"
print(p) # TypeError: unsupported operand types: <class 'str'>
__rmul__
class PointClass:
def __init__(self, x, y):
self.x = x
self.y = y
def __rmul__(self, other):
return PointClass(other * self.x, other * self.y)
def __str__(self):
return f"({self.x}, {self.y})"
p = PointClass(1, 2)
print(3 * p) # (3, 6)
__ceil__
对象向上取整,调用 math.ceil() 时自动调用。
import math
class PointClass:
def __init__(self, x, y):
self.x = x
self.y = y
def __str__(self):
return f"({self.x}, {self.y})"
def __ceil__(self):
return PointClass(math.ceil(self.x), math.ceil(self.y))
p = PointClass(3.4, 4.6)
print(math.ceil(p)) # (4, 5)
__floor__
对象向下取整,调用 math.floor() 时自动调用。
import math
class PointClass:
def __init__(self, x, y):
self.x = x
self.y = y
def __str__(self):
return f"({self.x}, {self.y})"
def __floor__(self):
return PointClass(math.floor(self.x), math.floor(self.y))
p = PointClass(3.4, 4.6)
print(math.floor(p)) # (3, 4)
__call__
使对象可调用。
class Calculator:
def __init__(self, x, y):
self.x = x
self.y = y
def __call__(self):
return self.x + self.y
calc = Calculator(3, 4)
result = calc()
print(result) # 7
class Calculator:
def __call__(self, a, b):
return a + b
calc = Calculator()
result = calc(3, 4)
print(result) # 7
__getattribute__
在每次访问属性时都会被调用,无论该属性是否存在。这意味着,如果你定义了这个方法,它会在你访问任何属性时(包括方法)被触发。
class Person:
def __getattribute__(self, item):
print(f"Access attribute {item}")
return super().__getattribute__(item)
def __getattr__(self, name):
return f"Attribute '{name}' does not exist."
p = Person()
print(p.age)
# Access attribute age
# Attribute 'age' does not exist.
__getattr__
在访问对象不存在的属性时调用。
class Person:
def __getattr__(self, name):
return f"Attribute '{name}' does not exist."
p = Person()
print(p.age) # Attribute 'age' does not exist.
__setattr__
当设置类实例属性时自动调用。
class Person:
def __setattr__(self, name, value):
print(f"Setting attribute '{name}' to '{value}'")
super().__setattr__(name, value)
p = Person()
p.name = "Alice" # Setting attribute 'name' to 'Alice'
print(p.name) # Alice
__delattr__
删除对象属性时自动调用。
class Person:
def __delattr__(self, name):
print(f"Deleting attribute '{name}'")
super().__delattr__(name)
p = Person()
p.name = "Alice"
print(p.name) # Alice
del p.name # Deleting attribute 'name'
print(p.name) # AttributeError: 'Person' object has no attribute 'name
__enter__
__enter__() 方法用于进入上下文管理器所定义的代码块之前执行的操作。
__exit__
上下文管理器的 __exit__() 方法还有三个参数,即异常类型、异常值和追踪信息。如果在 with 语句块中发生了异常,这些参数会传递给 __exit__() 方法,可以在该方法中进行相关的处理。
__del__
当对象不再被引用时,Python的垃圾回收机制会调用这个方法。
class FileHandler:
def __init__(self, filename):
self.filename = filename
def __enter__(self):
print("goto __enter__ open file")
self.file = open(self.filename, 'w', encoding='utf-8')
return self.file
def __exit__(self, exc_type, exc_val, exc_tb):
print("goto __exit__ close file")
self.file.close()
def __del__(self):
print("goto __del__ release resource")
print("process start")
with FileHandler("./test.txt") as f:
f.write("hello world!\n")
print("process end")
# process start
# goto __enter__ open file
# goto __exit__ close file
# goto __del__ release resource
# process end
__hash__
计算对象的 hash 值,hash() 时自动调用。
class PointClass:
def __init__(self, x, y):
self.x = x
self.y = y
def __hash__(self):
return hash(self.x) ^ hash(self.y)
p = PointClass(3, 4)
print(hash(p)) # 7
__or__
计算对象 或 的结果,使用 | 计算时自动调用。
class PointClass:
def __init__(self, x, y):
self.x = x
self.y = y
def __str__(self):
return f"({self.x}, {self.y})"
def __or__(self, other):
return PointClass(self.x | other.x, self.y | other.y)
p1 = PointClass(0, 0)
p2 = PointClass(3, 4) # 0011 0100
p3 = PointClass(5, 6) # 0101 0110
print(p1 | p2) # (3, 4)
print(p2 | p3) # (7, 6)
__and__
计算对象 与 的结果,使用 & 计算时自动调用。
class PointClass:
def __init__(self, x, y):
self.x = x
self.y = y
def __str__(self):
return f"({self.x}, {self.y})"
def __and__(self, other):
return PointClass(self.x & other.x, self.y & other.y)
p1 = PointClass(0, 0)
p2 = PointClass(3, 4) # 0011 0100
p3 = PointClass(5, 6) # 0101 0110
print(p1 & p2) # (3, 4)
print(p2 & p3) # (1, 4)
__xor__
计算对象 异或 的结果,使用 ^ 计算时自动调用。
class PointClass:
def __init__(self, x, y):
self.x = x
self.y = y
def __str__(self):
return f"({self.x}, {self.y})"
def __xor__(self, other):
return PointClass(self.x ^ other.x, self.y ^ other.y)
p1 = PointClass(0, 0)
p2 = PointClass(3, 4) # 0011 0100
p3 = PointClass(5, 6) # 0101 0110
print(p1 ^ p2) # (3, 4)
print(p2 ^ p3) # (6, 2)
魔法属性
__dict__
__dict__
包含通过 __init__
方法初始化的属性和值。
class TestClass:
def __init__(self, name):
self.name = name
def test(self):
self.age = 25
obj = TestClass("Alice")
print(obj.__dict__) # {'name': 'Alice'}
print(vars(obj)) # {'name': 'Alice'}
# print(TestClass.__dict__)
__doc__
输出指定对象的注释部分。
class PointClass:
"""
class of Point
"""
def __init__(self, x, y):
self.x = x
self.y = y
def __str__(self):
return f"({self.x}, {self.y})"
p1 = PointClass(0, 0)
print(p1.__doc__) # class of Point
__class__
获取指定对象的类别,通过在对象后面加上点号和__class__
即可访问__class__
属性。例如,obj.__class__
。也可以通过调用type(obj)
函数,也可以获得对象的__class__
属性。
class PointClass:
def __init__(self, x, y):
self.x = x
self.y = y
def __str__(self):
return f"({self.x}, {self.y})"
p1 = PointClass(0, 0)
print(p1.__class__) # <class '__main__.PointClass'>
print(type(p1)) # <class '__main__.PointClass'>
__slots__
内置类属性__slots__
是一个特殊的内置类属性,它可以用于定义类的属性名称的集合。一旦在类中定义了__slots__
属性,Python 将限制该类的实例只能拥有__slots__
中定义的属性。
class Person:
__slots__ = ('name', 'age')
def __init__(self, name, age):
self.name = name
self.age = age
self.address = "" # AttributeError: 'Person' object has no attribute 'address'
person = Person("Alice", 30)
print(person.name) # 输出 "Alice"
print(person.age) # 输出 30
print(person.__slots__) # ('name', 'age')
__annotations__
返回函数注解
def test(name: str) -> str:
return f"hello {name}"
print(test.__annotations__) # {'name': <class 'str'>, 'return': <class 'str'>}
__mro__
__mro__ 按照方法解析顺序列出各个超类。
class A:
def ping(self):
print('ping:', self)
class B(A):
def pong(self):
print('pong:', self)
class C(A):
def pong(self):
print('PONG:', self)
class D(B, C):
def ping(self):
super().ping()
print('post-ping:', self)
def pingpong(self):
self.ping()
super().ping()
self.pong()
super().pong()
C.pong(self)
if __name__ == '__main__':
d = D()
print(D.__mro__) # (<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>)
print(bool.__mro__) # (<class 'bool'>, <class 'int'>, <class 'object'>)
直接在类上调用实例方法时,必须显式传入 self 参数,因为这样访问的是未绑定方法。