当前位置:  首页>> 技术小册>> Python编程轻松进阶(五)

17.2 Python 的魔术方法

在Python中,魔术方法(Magic Methods)或称为特殊方法(Special Methods)是一系列双下划线(__)开头和结尾的方法。这些方法在Python的类定义中扮演着特殊角色,它们为Python对象提供了丰富的内建功能,如数值运算、对象比较、属性访问控制等。掌握这些魔术方法,能够让你更深入地理解Python的对象模型,进而编写出更加灵活、强大的代码。

17.2.1 构造与析构

1. __init__(self, ...)

这是类的构造函数,用于在创建对象时初始化对象的状态。当创建类的新实例时,__init__ 方法会自动被调用。

  1. class MyClass:
  2. def __init__(self, value):
  3. self.value = value
  4. obj = MyClass(10)
  5. print(obj.value) # 输出: 10

2. __new__(cls, ...)

__new__ 是一个静态方法,用于创建类的实例。在创建对象时,__new__ 方法首先被调用,然后才是 __init__ 方法。__new__ 必须返回一个类的实例,否则将引发 TypeError

  1. class Singleton:
  2. _instance = None
  3. def __new__(cls, *args, **kwargs):
  4. if not cls._instance:
  5. cls._instance = super().__new__(cls)
  6. return cls._instance
  7. def __init__(self):
  8. pass
  9. # 测试单例模式
  10. obj1 = Singleton()
  11. obj2 = Singleton()
  12. print(obj1 is obj2) # 输出: True

3. __del__(self)

析构函数,当对象被销毁时自动调用。用于执行清理操作,如关闭文件、释放资源等。

  1. class Resource:
  2. def __init__(self, name):
  3. self.name = name
  4. print(f"{name} created")
  5. def __del__(self):
  6. print(f"{self.name} destroyed")
  7. # 当Resource对象超出作用域或被显式删除时,__del__ 会被调用
  8. r = Resource("Resource1")
  9. del r # 输出: Resource1 destroyed

17.2.2 属性访问

1. __getattr__(self, name)

当尝试访问一个不存在的属性时,Python会调用此方法。可以用来实现动态属性或懒加载等特性。

  1. class LazyProperty:
  2. def __init__(self, func):
  3. self.func = func
  4. def __get__(self, instance, cls):
  5. if instance is None:
  6. return self
  7. result = self.func(instance)
  8. setattr(instance, self.func.__name__, result) # 将结果缓存为实例属性
  9. return result
  10. class MyClass:
  11. @LazyProperty
  12. def expensive_calculation(self):
  13. print("Calculating...")
  14. return 42
  15. obj = MyClass()
  16. print(obj.expensive_calculation) # 第一次调用,执行计算
  17. print(obj.expensive_calculation) # 第二次调用,从缓存中获取

2. __setattr__(self, name, value)

用于设置对象的属性值。重写此方法时,应小心避免无限递归,通常通过调用 super().__setattr__(name, value) 来处理普通属性的设置。

3. __getattribute__(self, name)

用于获取对象的属性值。与 __getattr__ 不同,__getattribute__ 会在任何属性访问时被调用,包括普通属性和特殊方法。因此,重写此方法时需要特别小心,以避免无限递归。

17.2.3 容器类型

1. 序列类型

  • __len__(self):返回容器中的元素数量。
  • __getitem__(self, key):根据索引或键获取容器中的元素。
  • __setitem__(self, key, value):设置容器中指定索引或键的元素值。
  • __delitem__(self, key):删除容器中指定索引或键的元素。

2. 迭代器与生成器

  • __iter__(self):返回一个迭代器对象。
  • __next__(self):返回容器的下一个元素,并在末尾引发 StopIteration 异常。

示例:实现一个简单的栈

  1. class Stack:
  2. def __init__(self):
  3. self.items = []
  4. def __len__(self):
  5. return len(self.items)
  6. def __getitem__(self, index):
  7. return self.items[index]
  8. def push(self, item):
  9. self.items.append(item)
  10. def pop(self):
  11. return self.items.pop()
  12. def __iter__(self):
  13. return iter(self.items[::-1]) # 返回逆序迭代器
  14. # 使用栈
  15. s = Stack()
  16. s.push(1)
  17. s.push(2)
  18. s.push(3)
  19. print(s[0]) # 输出: 3
  20. for item in s:
  21. print(item) # 输出: 3, 2, 1

17.2.4 数值运算

Python中的数值类型(如整数、浮点数)支持丰富的运算符,如加法(+)、减法(-)等。通过定义特定的魔术方法,可以让自定义的类也支持这些运算。

  • __add__(self, other):加法。
  • __sub__(self, other):减法。
  • __mul__(self, other):乘法。
  • __truediv__(self, other):真除法。
  • __floordiv__(self, other):整数除法(地板除)。
  • __mod__(self, other):取模。
  • __pow__(self, other[, modulo]):幂运算。

示例:实现一个简单的二维向量类

  1. class Vector2D:
  2. def __init__(self, x=0, y=0):
  3. self.x = x
  4. self.y = y
  5. def __add__(self, other):
  6. return Vector2D(self.x + other.x, self.y + other.y)
  7. def __mul__(self, scalar):
  8. return Vector2D(self.x * scalar, self.y * scalar)
  9. def __str__(self):
  10. return f"Vector2D({self.x}, {self.y})"
  11. # 使用Vector2D
  12. v1 = Vector2D(1, 2)
  13. v2 = Vector2D(3, 4)
  14. print(v1 + v2) # 输出: Vector2D(4, 6)
  15. print(v1 * 2) # 输出: Vector2D(2, 4)

17.2.5 比较与排序

  • __eq__(self, other):等于。
  • __ne__(self, other):不等于。
  • __lt__(self, other):小于。
  • __le__(self, other):小于等于。
  • __gt__(self, other):大于。
  • __ge__(self, other):大于等于。

这些魔术方法定义了对象之间的比较行为,使得自定义对象可以像内置类型一样被比较和排序。

总结

Python的魔术方法是Python对象模型的核心部分,它们为Python对象提供了丰富的行为和特性。通过定义和重写这些魔术方法,可以使得自定义的类更加灵活、强大,更加符合Python的编程习惯。然而,在重写这些魔术方法时,也需要特别注意避免无限递归、保持方法的通用性和一致性等问题。希望本章的内容能够帮助你更好地理解Python的魔术方法,并在实际编程中灵活运用它们。