前言
大家好,我是 倔强青铜三 。欢迎关注我,微信公众号:倔强青铜三。欢迎点赞、收藏、关注,一键三连!
欢迎来到 苦练Python第56天!
今天我们把聚光灯打在最容易被忽视、却又最魔法的 28 个双下方法上: 元类、描述符、异步协议、Pickle 协议以及对象模型"暗桩"。
读完你会知道:Python 什么时候偷偷调它们、如何定制、如何防坑。
📦 目录(28 个方法,一口气干完)
-
属性访问三兄弟
__getattr__
__getattribute__
__setattr__
__delattr__
-
可调用 & 类型检查
__call__
__instancecheck__
__subclasscheck__
-
异步三件套
__await__
__aiter__
__anext__
-
Pickle & 拷贝
__reduce__
__reduce_ex__
__copy__
__deepcopy__
-
泛型 & 继承钩子
__class_getitem__
__mro_entries__
__init_subclass__
-
描述符协议
__get__
__set__
__delete__
__set_name__
-
杂项暗桩
__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 分钟)
- 写一个描述符
Range
,让属性值只能落在[min, max]
。 - 利用
__init_subclass__
做一个插件系统,自动收集所有子类。 - 用
__reduce__
实现一个数据库连接池的序列化(仅保存配置)。 - 写一个异步生成器,支持
async for line in tail("log.txt"):
。
📌 记忆口诀
- 访问属性:
getattr
兜底,getattribute
全拦。 - 异步:
await
看__await__
,async for
看__aiter__
+__anext__
。 - Pickle:想活命,先写
__reduce__
。 - 描述符:谁用谁知道,省代码省 BUG。
最后感谢阅读!欢迎关注我,微信公众号 :
倔强青铜三
。一键三连(点赞、收藏、关注)!