苦练Python第56天:元类•描述符•异步•Pickle 的 28 个魔术方法——从入门到精通

前言

大家好,我是 倔强青铜三 。欢迎关注我,微信公众号:倔强青铜三。欢迎点赞、收藏、关注,一键三连!

欢迎来到 苦练Python第56天

今天我们把聚光灯打在最容易被忽视、却又最魔法的 28 个双下方法上: 元类、描述符、异步协议、Pickle 协议以及对象模型"暗桩"

读完你会知道:Python 什么时候偷偷调它们、如何定制、如何防坑。

📦 目录(28 个方法,一口气干完)

  1. 属性访问三兄弟
    __getattr__ __getattribute__ __setattr__

    __delattr__

  2. 可调用 & 类型检查
    __call__ __instancecheck__

    __subclasscheck__

  3. 异步三件套
    __await__ __aiter__

    __anext__

  4. Pickle & 拷贝
    __reduce__ __reduce_ex__

    __copy__ __deepcopy__

  5. 泛型 & 继承钩子
    __class_getitem__ __mro_entries__

    __init_subclass__

  6. 描述符协议
    __get__ __set__

    __delete__ __set_name__

  7. 杂项暗桩
    __slots__ __dir__ __doc__

    __module__ __annotations__


1. 属性访问三兄弟 + 一个隐藏 Boss

1.1 __getattr__ ------ "找不到才找我"

触发时机

  • 访问不存在的属性时
  • 不会拦截已存在的属性

作用

  • 实现懒加载、动态 API、回退字典

示例:懒加载配置文件

python 复制代码
class Config:
    def __init__(self, file):
        self._file = file
        self._data = None

    def _load(self):
        import json, pathlib
        self._data = json.loads(pathlib.Path(self._file).read_text())

    def __getattr__(self, name):
        if self._data is None:
            self._load()
        try:
            return self._data[name]
        except KeyError:
            raise AttributeError(name)

cfg = Config("settings.json")
print(cfg.debug)   # 第一次触发 __getattr__ 并加载文件

1.2 __getattribute__ ------ "所有属性我都要管"

触发时机

  • 任何属性访问都会触发(包括存在和不存在的)

作用

  • 实现权限控制、审计、代理

⚠️ 注意:

无限递归地狱 return self.x → 正确姿势:object.__getattribute__(self, name)

示例:禁止访问私有字段

python 复制代码
class Guard:
    def __getattribute__(self, name):
        if name.startswith("_"):
            raise PermissionError("Private!")
        return object.__getattribute__(self, name)

g = Guard()
g.ok = 1
print(g.ok)   # 1
# g._secret   # PermissionError

1.3 __setattr__ & 1.4 __delattr__

触发时机

  • obj.x = y / del obj.x

示例:只读属性保护

python 复制代码
class ReadOnly:
    __slots__ = ("_x",)

    def __init__(self, x):
        object.__setattr__(self, "_x", x)

    @property
    def x(self):
        return self._x

    def __setattr__(self, key, value):
        raise AttributeError("read-only")

ro = ReadOnly(42)
# ro.x = 100   # AttributeError

2. 可调用 & 类型检查

2.1 __call__ ------ "把对象当函数"

触发时机

  • obj()

示例:计数器闭包

python 复制代码
class Counter:
    def __init__(self):
        self.n = 0

    def __call__(self):
        self.n += 1
        return self.n

c = Counter()
print(c(), c())   # 1 2

2.2 __instancecheck__ & 2.3 __subclasscheck__

触发时机

  • isinstance(obj, cls) / issubclass(sub, cls)

示例:抽象鸭子类型

python 复制代码
class DrawableMeta(type):
    def __instancecheck__(self, instance):
        return hasattr(instance, "draw")

class Drawable(metaclass=DrawableMeta):
    pass

class Circle:
    def draw(self): ...

print(isinstance(Circle(), Drawable))  # True

3. 异步三件套

3.1 __await__ ------ 让对象 awaitable

触发时机

  • await obj

示例:自定义 Future(极简)

python 复制代码
class MyFuture:
    def __init__(self, value):
        self.value = value

    def __await__(self):
        yield  # 让出一次控制权
        return self.value

async def main():
    result = await MyFuture(99)
    print(result)

import asyncio
asyncio.run(main())   # 99

3.2 __aiter__ & 3.3 __anext__

触发时机

  • async for i in obj:

示例:异步计数器

python 复制代码
class AsyncCounter:
    def __init__(self, n):
        self.max = n
        self.i = 0

    def __aiter__(self):
        return self

    async def __anext__(self):
        if self.i >= self.max:
            raise StopAsyncIteration
        self.i += 1
        return self.i

async def main():
    async for n in AsyncCounter(3):
        print(n)

import asyncio
asyncio.run(main())   # 1 2 3

4. Pickle & 拷贝

4.1 __reduce__ & 4.2 __reduce_ex__

触发时机

  • pickle.dumps(obj) / 指定协议版本

作用

  • 自定义序列化规则,返回 (callable, args) 或更复杂的元组

示例:序列化打开的文件句柄(仅文件名)

python 复制代码
class FileWrapper:
    def __init__(self, path):
        self.path = path
        self.file = open(path, "rb")

    def __reduce__(self):
        return (self.__class__, (self.path,))

    def __del__(self):
        if hasattr(self, "file"):
            self.file.close()

import pickle, tempfile, os
fd, name = tempfile.mkstemp()
os.write(fd, b"Hi")
os.close(fd)

fw = FileWrapper(name)
data = pickle.dumps(fw)
new_fw = pickle.loads(data)
print(new_fw.path)   # /tmp/...

4.3 __copy__ & 4.4 __deepcopy__

触发时机

  • copy.copy(obj) / copy.deepcopy(obj)

示例:共享缓存的浅拷贝

python 复制代码
import copy

class Cache:
    def __init__(self):
        self.data = {}

    def __copy__(self):
        new = Cache()
        new.data = self.data  # 共享
        return new

c1 = Cache()
c2 = copy.copy(c1)
c1.data["x"] = 1
print(c2.data)  # {'x': 1}

5. 泛型 & 继承钩子

5.1 __class_getitem__ ------ 支持 MyList[int]

触发时机

  • 类索引语法 cls[T]

示例:PEP 560 泛型工厂

python 复制代码
class MyList:
    def __init__(self):
        self.items = []

    def __class_getitem__(cls, item):
        # 实际库中会返回 _GenericAlias
        print(f"Generic alias created with {item}")
        return f"MyList[{item.__name__}]"

print(MyList[int])   # MyList[int]

5.2 __mro_entries__ ------ 操纵 MRO

触发时机

  • 类定义体内使用 class C(Base, Mixin) 且 Mixin 是特殊对象

示例:动态混入方法

python 复制代码
class Addable:
    def __mro_entries__(self, bases):
        return (Helper,)   # 插入到 MRO

class Helper:
    def add(self, other): return self + other

class MyInt(int, Addable()):
    pass

print(MyInt.__mro__)
# (<class '__main__.MyInt'>, <class '__main__.Helper'>, <class 'int'>, ...)

5.3 __init_subclass__ ------ 注册子类

触发时机

  • 定义子类后自动调用

示例:API 路由注册

python 复制代码
class Router:
    routes = {}

    def __init_subclass__(cls, path=None, **kw):
        super().__init_subclass__(**kw)
        if path:
            Router.routes[path] = cls

class Index(Router, path="/"):
    pass

print(Router.routes)  # {'/': <class '__main__.Index'>}

6. 描述符协议

6.1 __get__(self, instance, owner)

6.2 __set__(self, instance, value)

6.3 __delete__(self, instance)

6.4 __set_name__(self, owner, name) ------ PEP 487

触发时机

  • 作为类变量被访问 / 赋值 / 删除 / 类创建时

示例:类型检查字段

python 复制代码
class Typed:
    def __set_name__(self, owner, name):
        self.name = name

    def __get__(self, instance, owner):
        if instance is None:
            return self
        return instance.__dict__[self.name]

    def __set__(self, instance, value):
        if not isinstance(value, int):
            raise TypeError("Need int")
        instance.__dict__[self.name] = value

class Point:
    x = Typed()
    y = Typed()

p = Point()
p.x = 3
# p.y = "hi"  # TypeError

7. 杂项暗桩

方法 触发时机 一句话用法
__slots__ 类定义 节省内存,禁止 __dict__
__dir__ dir(obj) 定制自动补全
__doc__ help(obj) 文档字符串
__module__ 模块名 序列化/文档用
__annotations__ 类型注解 运行时读取

示例:精简对象内存

python 复制代码
class Point:
    __slots__ = ("x", "y")
    def __init__(self, x, y):
        self.x, self.y = x, y

p = Point(1, 2)
# p.z = 3   # AttributeError

🧪 实战演练(30 分钟)

  1. 写一个描述符 Range,让属性值只能落在 [min, max]
  2. 利用 __init_subclass__ 做一个插件系统,自动收集所有子类。
  3. __reduce__ 实现一个数据库连接池的序列化(仅保存配置)。
  4. 写一个异步生成器,支持 async for line in tail("log.txt"):

📌 记忆口诀

  • 访问属性:getattr 兜底,getattribute 全拦。
  • 异步:await__await__async for__aiter__ + __anext__
  • Pickle:想活命,先写 __reduce__
  • 描述符:谁用谁知道,省代码省 BUG。

最后感谢阅读!欢迎关注我,微信公众号倔强青铜三

一键三连(点赞、收藏、关注)!

相关推荐
倔强青铜三3 小时前
苦练Python第55天:容器协议的七个魔术方法从入门到精通
人工智能·python·面试
伊织code3 小时前
LLM - 命令行与Python库的大语言模型交互工具
开发语言·python·语言模型
空中湖3 小时前
AI觉醒:小白的大模型冒险记 第9章:GPT大师的工坊 - 语言模型的训练秘密
人工智能·gpt·语言模型
whaosoft-1433 小时前
51c大模型~合集187
人工智能
春生黎至10053 小时前
Python列表
开发语言·python
救救孩子把3 小时前
8-机器学习与大模型开发数学教程-第0章 预备知识-0-8 编程与数值计算基础(浮点数精度、溢出、数值稳定性)
人工智能·机器学习
lalala_Zou3 小时前
虾皮后端一面
java·面试
伊织code3 小时前
Klavis AI - MCP 集成层让 AI 代理可靠使用数千种工具
人工智能·klavis ai
愈努力俞幸运3 小时前
socket编程 netstat 大小端 rpc 协程 io yield
网络·python·网络协议·rpc