Python 之魔法方法草稿笔记

魔法方法

魔法方法,也就是特殊方法,是以双下划线开头和结尾的方法,比如__init__、__str__这些。它们被Python解释器自动调用,用来实现对象的特定行为。python 中有很多这样的魔法方法。为了了解清楚各个魔法方法的用处,特此做了一个笔记。

通常魔法方法可以分为几个大类:构造与初始化字符串表示属性访问容器类比较操作算术运算迭代器上下文管理等等。

说明

首先,构造与初始化最常用的是__init__,还有__new__。用户可能知道__init__是初始化方法,但__new__是实际创建实例的方法。如果用户没有重写以上魔法方法,在创建一个对象时,会先执行__new__ 创建对象 然后执行__init__进行初始化对象。这样用户就可以获取到一个完整的对象

然后是字符串表示,比如__str__和__repr__,这两个方法经常被混淆。需要说明它们的不同用途,__str__用于用户友好的字符串表示,而__repr__用于开发者调试,通常可以用eval()重新生成对象。

接下来是属性访问,比如__getattr__ |__setattr__ |__delattr__,这些方法用于管理属性的访问、设置和删除。还有__getattribute__,但要注意使用时的递归问题。

容器类的方法包括__len__ |__getitem__|__setitem__|__delitem__,这些让对象表现得像列表或字典。迭代相关的有__iter__和__next__,用于支持循环操作。

比较操作的方法,比如__eq__|__lt__等,用来重载比较运算符。算术运算的方法如__add__|__sub__,允许对象进行数学运算。还有反向运算和增量赋值的方法,比如__radd__|__iadd__。

上下文管理是通过__enter__和__exit__实现的,用于with语句,处理资源的获取和释放。

此外,还有__call__方法,让实例可以像函数一样被调用,__getstate__和__setstate__用于序列化,__slots__用来优化内存使用等。

总结与示例

对象的构造与销毁

方法触发场景典型应用
_new_(cls, …)实例创建时.单例模式、不可变类
_init_(self, …)实例初始化时.属性赋值、资源初始化
_del_(self)对象销毁时.资源清理(不推荐依赖

通过代码演示:


class Student(object):
    def __new__(cls, *args, **kwargs):
        print("__new__ 方法执行了。。。。")
        return super().__new__(cls) # 注意 __new__ 方法是返回创建好的空对象,一定要返回,不然__init__方法不会被调用

    def __init__(self, name, score):
        print("__init__ 方法执行了。。。。")
        self.name = name
        self.score = score


if __name__ == "__main__":
    student = Student("张三", 100)

输出:

__new__ 方法执行了。。。。
__init__ 方法执行了。。。。

进程已结束,退出代码为 0

可以看到,当我们创建对象的时候,是先执行的__new__ 然后在执行__init__ 进行对象初始化

字符串类型

方法触发场景典型应用
_str_(self)print(obj)/str(obj)用户友好展示
_repr_(self)交互式环境/repr(obj)明确对象信息,可用eval解析

代码展示:

from datetime import date

today = date(2025, 3, 3)

print(str(today))   # 输出: 2023-10-05 (用户友好)
print(repr(today))  # 输出: datetime.date(2023, 10, 5) (可eval重建对象)

class Product:
    def __init__(self, name, price):
        self.name = name
        self.price = price

    def __str__(self):
        print("__str__ 方法执行了。。。。")
        return f"{self.name} ¥{self.price}"

    def __repr__(self):
        print("__repr__ 方法执行了。。。。")
        return f"Product(name='{self.name}', price={self.price})"

p = Product("Python书籍", 99.9)
print(p)        # Python书籍 ¥99.9
print(repr(p))  # Product(name='Python书籍', price=99.9)

设计原则

  • _str_:假设读者是最终用户,展示关键信息
  • _repr_:假设读者是开发者,包含足够调试信息
  • 黄金规则:eval(repr(obj)) 应能重建对象(理想情况)

对象属性类

方法触发场景典型应用
_getattr_(self, name)访问不存在的属性时处理缺失属性逻辑
_getattribute_(self, name)每次属性访问时控制所有属性访问
_setattr_(self, name, value)设置属性时拦截属性赋值操作
_delattr_(self, name)删除属性时拦截属性删除操作
class  Attr:

    def __getattr__(self, item):
        print("__getattr__ 方法执行了。。。。")
        raise AttributeError(f"{item} 不存在")

    def __setattr__(self, key, value):
        print("__setattr__ 方法执行了。。。。")
        super().__setattr__(key, value)
    def __delattr__(self, item):
        print("__delattr__ 方法执行了。。。。")
        del self.__dict__[item]

    def __getattribute__(self, item):
        print("__getattribute__ 方法执行了。。。。")
        return super().__getattribute__(item)


att = Attr()
att.existing = "test"
print("=" * 10)
print(att.existing)
# 流程:
# 1. __getattribute__('existing') → 找到属性值
# 2. __getattr__ 不执行
print("=" * 10)
print(att.non_existing)
# 流程:
# 1. __getattribute__('non_existing') → 抛出AttributeError
# 2. 解释器捕获异常后调用__getattr__('non_existing')

del att.existing
# 流程:
# 1. __delattr__('existing') -> __getattribute__获取元素。
# 2. 删除元素。

容器类型

方法触发场景典型应用
_len_(self)len(obj)返回整数
_getitem_(self, key)obj[key]获取元素,支持索引/切片
_setitem_(self, key, value)obj[key] = value修改元
_contains_(self, item)item in obj成员判断

代码展示:

class CustomList:
    # 初始化对象
    def __init__(self, data):
        self.data = list(data)
    def __len__(self):
        print("__len__ 方法执行了。。。。")
        return len(self.data) # 获取长度
    def __getitem__(self, index):
        print("__getitem__ 方法执行了。。。。")
        return self.data[index] # 获取索引对应的值
    def __setitem__(self, index, value):
        print("__setitem__ 方法执行了。。。。")
        self.data[index] = value # 设置索引对应的值
    def __contains__(self, item):
        print("__contains__ 方法执行了。。。。")
        return item in self.data # 判断是否包含某元素

cl = CustomList([1, 2, 3])
print(len(cl))  #  __len__ 方法执行了。。。。       3 
print(cl[1])  #    __getitem__ 方法执行了。。。。   2
print(3 in cl)  #  __contains__ 方法执行了。。。。  True

运算符重载

方法运算符反向方法
_add_(self, other)+_radd_(self, other)
_sub_(self, other)-_rsub_(self, other)
_mul_(self, other)*_rmul_(self, other)
_eq_(self, other)==

代码展示:

class Meter:
    def __init__(self, value):
        self.value = value

    def __add__(self, other):
        print("触发 __add__")
        if isinstance(other, Meter):
            return Meter(self.value + other.value)
        elif isinstance(other, (int, float)):
            return Meter(self.value + other)
        return NotImplemented

    def __radd__(self, other):
        print("触发 __radd__")
        return self + other  # 委托给 __add__

# 测试案例
m = Meter(3)
print((m + 2).value)      # 正常调用 __add__ → Meter(5)
print((2 + m).value) # 触发流程:
                  # 1. int(2) 无 __add__ 处理 Meter
                  # 2. 调用 m.__radd__(2) → Meter(5)
# 输出:
# 触发 __add__
# 5
# 触发 __radd__
# 触发 __add__
# 5    

举例说明 如果 a + b 如果a 中_add_ 方法能处理这个运算,则执行。如果处理不了,则会调用b 的__radd__方法去处理。
设计原则

  • 对称性:实现正向方法时应考虑反向方法
  • 类型检查:在反向方法中验证操作数类型
  • 性能优化:避免在反向方法中重复正向逻辑

上下文管理

方法触发场景典型应用
_enter_(self)with语句开始时返回需要管理的资源
_exit_(self, exc_type, exc_val, traceback)with语句结束时处理异常,释放资源

代码演示:

class SafeFile:
    def __init__(self, filename):
        self.filename = filename
    def __enter__(self):
        print("__enter__ 方法执行了。。。。")
        self.file = open(self.filename, 'r')
        return self.file

    def __exit__(self, exc_type, exc_val, traceback):
        print("__exit__ 方法执行了。。。。")
        self.file.close() # 关闭文件
        if exc_type is not None: # 如果有异常,则打印异常信息
            print(f"Error occurred: {exc_val}")
        return True  # 抑制异常

with SafeFile('../test.txt') as f:
    content = f.read()

# 控制台输出:
# __enter__ 方法执行了。。。。
# __exit__ 方法执行了。。。。

高级魔法方法应用

描述符协议

方法作用应用场景
_get_(self, instance, owner)获取属性值数据验证、延迟计算
_set_(self, instance, value)设置属性值类型检查、触发更新

代码演示:

class Typed:
    def __init__(self, type_):
        self.type = type_  # 存储期望的数据类型

    def __set_name__(self, owner, name):
        print(f"__set_name__ 方法执行了。。。。{self},{owner},{name}")
        # 自动获取属性名称(Python 3.6+)
        self.name = name

    def __get__(self, instance, owner):
        print("__get__ 方法执行了。。。。")
        # 直接从实例字典中取值
        return instance.__dict__.get(self.name)

    def __set__(self, instance, value):
        print("__set__ 方法执行了。。。。")
        # 类型检查核心逻辑
        if not isinstance(value, self.type):
            raise TypeError(f"{self.name} 必须是 {self.type}")
        instance.__dict__[self.name] = value

class Person:
    name = Typed(str)  # 类属性绑定描述符
    age = Typed(int)   # 每个描述符实例独立工作

# 使用示例
p = Person()
p.name = "wyz"      # ✅ 合法
p.age = 18            # ✅ 合法

try:
    p.name = 123      # ❌ 触发 TypeError: name 必须是 <class 'str'>
except TypeError as e:
    print(e)

try:
    p.age = "25"      # ❌ 触发 TypeError: age 必须是 <class 'int'>
except TypeError as e:
    print(e)
# 输出:
# __set_name__ 方法执行了。。。。<__main__.Typed object at 0x11d4b6b50>,<class '__main__.Person'>,name
# __set_name__ 方法执行了。。。。<__main__.Typed object at 0x11d4b6af0>,<class '__main__.Person'>,age
# __set__ 方法执行了。。。。
# __set__ 方法执行了。。。。
# __set__ 方法执行了。。。。
# name 必须是 <class 'str'>
# __set__ 方法执行了。。。。
# age 必须是 <class 'int'>

迭代器协议

方法迭代阶段实现要求
_iter_(self)迭代开始返回迭代器对象
_next_(self)每次迭代取值抛出StopIteration终止

代码示例:

class Fibonacci:
    def __init__(self, limit):
        self.a = 0
        self.b = 1
        self.limit = limit

    def __iter__(self):
        return self

    def __next__(self):
        if self.a > self.limit:
            raise StopIteration
        current = self.a
        self.a, self.b = self.b, self.a + self.b
        return current

for n in Fibonacci(100):
    print(n)  # 输出0,1,1,2,3,5...89

可调用对象协议(call)

让实例像函数一样被调用。这通常用于创建可调用对象,比如装饰器类。我需要解释__call__的作用。

示例1:实例像函数一样被调用

class Accumulator:
    def __init__(self):
        self.total = 0
    
    def __call__(self, value):
        self.total += value
        return self.total

acc = Accumulator()
print(acc(5))   # 输出: 5
print(acc(10))  # 输出: 15

示例2: 使用__call__ 实现一个装饰器

class LoggerDecorator:
    def __init__(self, func):
        self.func = func
    
    def __call__(self, *args):
        print(f"调用函数: {self.func.__name__}")
        return self.func(*args)

@LoggerDecorator
def add(a, b):
    return a + b

print(add(3,5))  # 先输出日志再返回结果

序列化控制__getstate__和__setstate__的使用

用于序列化。当对象被pickle时,默认会保存__dict__,但有时需要控制序列化的数据

方法触发时机主要作用
_getstate_对象被序列化时返回可序列化的状态字典
_setstate_对象反序列化时根据状态重建对象
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值