,

Python 魔术方法详细

魔术方法(Magic Methods)是 Python 中以双下划线开头和结尾的特殊方法,如 __init__…

魔术方法(Magic Methods)是 Python 中以双下划线开头和结尾的特殊方法,如 __init____str__。它们不需要显式调用,而是由 Python 在特定场景下自动触发

字符串表示

当使用 print()str()repr() 时,Python 会自动调用对应的魔术方法:

class Point:
   def __init__(self, x, y):
       self.x = x
       self.y = y

   def __str__(self):
       """面向用户,友好的可读格式"""
       return f"Point({self.x}, {self.y})"

   def __repr__(self):
       """面向开发者,精确的重建格式"""
       return f"Point({self.x!r}, {self.y!r})"


p = Point(3, 4)
print(p)           # Point(3, 4) —— 调用 __str__
print(str(p))      # Point(3, 4) —— 调用 __str__
print(repr(p))     # Point(3, 4) —— 调用 __repr__

# 交互式环境中直接显示对象,调用 __repr__
# p # Point(3, 4)

建议: 两个方法都实现。如果只实现 __repr____str__ 会回退到使用它。


比较操作

通过实现比较魔术方法,可以让自定义对象支持 ==<> 等操作:

class Person:
   def __init__(self, name, age):
       self.name = name
       self.age = age

   def __eq__(self, other):
       """=="""
       if not isinstance(other, Person):
           return NotImplemented
       return self.age == other.age

   def __lt__(self, other):
       """<"""
       if not isinstance(other, Person):
           return NotImplemented
       return self.age < other.age

   def __le__(self, other):
       """<="""
       return self < other or self == other

   def __gt__(self, other):
       """>"""
       return not self <= other

   def __ge__(self, other):
       """>="""
       return not self < other

   def __ne__(self, other):
       """!="""
       return not self == other

   def __repr__(self):
       return f"Person({self.name!r}, {self.age})"


alice = Person("Alice", 30)
bob = Person("Bob", 25)

print(alice == bob)  # False  
print(alice > bob)   # True
print(alice <= bob)  # False

# 实现了比较方法后,可以使用 sorted
people = [bob, alice]
print(sorted(people))  # [Person('Bob', 25), Person('Alice', 30)]

简化方案: 使用 @functools.total_ordering 装饰器,只需实现 __eq__ 和其中一个(如 __lt__),其余会自动推导:

from functools import total_ordering

@total_ordering
class Person:
   def __init__(self, name, age):
       self.name = name
       self.age = age

   def __eq__(self, other):
       if not isinstance(other, Person):
           return NotImplemented
       return self.age == other.age

   def __lt__(self, other):
       if not isinstance(other, Person):
           return NotImplemented
       return self.age < other.age

   def __repr__(self):
       return f"Person({self.name!r}, {self.age})"

算术运算

让对象支持 +-*/ 等运算符:

class Vector:
   def __init__(self, x, y):
       self.x = x
       self.y = y

   def __add__(self, other):
       """+"""
       if isinstance(other, Vector):
           return Vector(self.x + other.x, self.y + other.y)
       return NotImplemented  # 返回 NotImplemented,让 Python 尝试 other 的 __radd__

   def __sub__(self, other):
       """-"""
       if isinstance(other, Vector):
           return Vector(self.x - other.x, self.y - other.y)
       return NotImplemented

   def __mul__(self, scalar):
       """*,向量与标量相乘"""
       if isinstance(scalar, (int, float)):
           return Vector(self.x * scalar, self.y * scalar)
       return NotImplemented

   def __rmul__(self, scalar):
       """右乘:scalar * vector"""
       return self * scalar  # 复用 __mul__

   def __truediv__(self, scalar):
       """/"""
       if isinstance(scalar, (int, float)):
           return Vector(self.x / scalar, self.y / scalar)
       return NotImplemented

   def __neg__(self):
       """负号:-vector"""
       return Vector(-self.x, -self.y)

   def __abs__(self):
       """abs()"""
       return (self.x ** 2 + self.y ** 2) ** 0.5

   def __repr__(self):
       return f"Vector({self.x}, {self.y})"


v1 = Vector(1, 2)
v2 = Vector(3, 4)

print(v1 + v2)       # Vector(4, 6)
print(v2 - v1)       # Vector(2, 2)
print(v1 * 3)        # Vector(3, 6)
print(2 * v1)        # Vector(2, 4) —— 调用 __rmul__
print(-v1)           # Vector(-1, -2)
print(abs(v1))       # 2.236...

常用算术魔术方法:

运算符魔术方法说明
+__add__加法
-__sub__减法
*__mul__乘法
/__truediv__真除法
//__floordiv__整除
%__mod__取模
**__pow__幂运算
+a__pos__正号
-a__neg__负号
abs()__abs__绝对值

容器协议

实现容器协议,让自定义对象可以像 listdict 一样使用 []len()in 等操作:

class ShoppingCart:
   def __init__(self):
       self._items = []

   def __len__(self):
       """len(cart)"""
       return len(self._items)

   def __getitem__(self, index):
       """cart[index]"""
       return self._items[index]

   def __setitem__(self, index, value):
       """cart[index] = value"""
       self._items[index] = value

   def __delitem__(self, index):
       """del cart[index]"""
       del self._items[index]

   def __contains__(self, item):
       """item in cart"""
       return item in self._items

   def __iter__(self):
       """for item in cart"""
       return iter(self._items)

   def append(self, item):
       self._items.append(item)

   def __repr__(self):
       return f"ShoppingCart({self._items!r})"


cart = ShoppingCart()
cart.append("apple")
cart.append("banana")
cart.append("orange")

print(len(cart))           # 3
print(cart[0])             # apple
print(cart[1:])            # ['banana', 'orange'] —— 支持切片
print("apple" in cart)     # True

for item in cart:
   print(item)
# apple
# banana
# orange

类型转换

实现类型转换魔术方法,让对象支持 int()float()bool() 等转换:

class Money:
   def __init__(self, amount):
       self.amount = amount

   def __int__(self):
       return int(self.amount)

   def __float__(self):
       return float(self.amount)

   def __bool__(self):
       return self.amount != 0

   def __repr__(self):
       return f"Money({self.amount})"


m = Money(100.5)
print(int(m))      # 100
print(float(m))    # 100.5
print(bool(m))     # True

m0 = Money(0)
print(bool(m0))    # False

属性访问拦截

通过实现属性访问相关的魔术方法,可以拦截对对象属性的读取设置删除操作:

class Config:
   def __init__(self):
       # 必须用 object.__setattr__,否则会无限递归
       object.__setattr__(self, "_data", {})

   def __getattr__(self, name):
       """访问不存在的属性时触发"""
       if name in self._data:
           return self._data[name]
       raise AttributeError(f"'{type(self).__name__}' 对象没有属性 '{name}'")

   def __setattr__(self, name, value):
       """设置任意属性时触发"""
       if name.startswith("_"):
           # 内部属性直接设置,避免递归
           object.__setattr__(self, name, value)
       else:
           self._data[name] = value

   def __delattr__(self, name):
       """删除属性时触发"""
       if name in self._data:
           del self._data[name]
       else:
           raise AttributeError(f"'{type(self).__name__}' 对象没有属性 '{name}'")

   def __repr__(self):
       return f"Config({self._data!r})"


cfg = Config()
cfg.debug = True      # 调用 __setattr__
cfg.port = 8080       # 调用 __setattr__
print(cfg.debug)      # True —— 调用 __getattr__
print(cfg.port)       # 8080 —— 调用 __getattr__
del cfg.debug         # 调用 __delattr__
print(cfg)            # Config({'port': 8080})

注意: __setattr__ 拦截所有属性设置。如果在其内部使用 self.xxx = value 的方式赋值,会再次触发 __setattr__,导致无限递归。应使用 object.__setattr__(self, name, value) 来绕过拦截。


__getattr__ vs __getattribute__

  • __getattr__在访问不存在的属性时触发
  • __getattribute__:访问任何属性时都会触发(更底层,优先级更高)
class Demo:
   def __init__(self):
       self.existing = 100

   def __getattribute__(self, name):
       """所有属性访问都会经过这里"""
       print(f"正在访问: {name}")
       # 必须用 object.__getattribute__,否则会无限递归
       return object.__getattribute__(self, name)

   def __getattr__(self, name):
       """只有访问不存在的属性时才到这里"""
       return f"'{name}' 不存在,返回默认值"


d = Demo()
print(d.existing)   # 先触发 __getattribute__,返回 100
print(d.missing)    # 先触发 __getattribute__,找不到,再触发 __getattr__

⚠️ 警告:__getattribute__ 中再次访问 self.xxx 也会触发自身,必须使用 object.__getattribute__(self, name)


对象生命周期

除了 __init__,还有 __del__ 在对象被销毁时调用:

class DatabaseConnection:
   def __init__(self, db_name):
       self.db_name = db_name
       print(f"连接到数据库: {db_name}")

   def __del__(self):
       """对象被销毁时调用"""
       print(f"关闭数据库连接: {self.db_name}")


conn = DatabaseConnection("test_db")
del conn  # 关闭数据库连接: test_db

注意: __del__ 的调用时机不确定(取决于垃圾回收),不应依赖它做关键清理。对于资源管理,应使用上下文管理器


常用魔术方法速查

类别方法触发场景
构造__init__创建对象后初始化
构造__new__创建对象(已讲过)
字符串__str__print()str()
字符串__repr__repr()、交互式显示
比较__eq__==
比较__lt__<
比较__gt__>
比较__le__<=
比较__ge__>=
比较__ne__!=
算术__add__+
算术__sub__-
算术__mul__*
算术__truediv__/
容器__len__len()
容器__getitem__obj[key]
容器__setitem__obj[key] = value
容器__delitem__del obj[key]
容器__contains__in
容器__iter__for...in
转换__int__int()
转换__float__float()
转换__bool__bool()
可调用__call__obj()
属性__getattr__访问不存在的属性
属性__getattribute__访问任意属性
属性__setattr__设置属性
属性__delattr__删除属性
生命周期__del__对象销毁

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

About the Author

每个人都有自己得时区,在自己得时区里,一切都是准时的。

BlockSpare — News, Magazine and Blog Addons for (Gutenberg) Block Editor