魔术方法,Magic Methods/Dunder Methods,这个名字真够奇怪的,实际意义是Python 对象协议的一部分,用来让你的类“接入 Python 语言本身的行为系统”。通俗点来讲,就是增加一些特定操作的快捷方式。
- 生命周期:__new__(创建实例,返回实例)与 __init__(初始化实例)的区别。
- 字符串表示:__str__(面向用户)与 __repr__(面向开发者/调试)。
- 容器类:__getitem__, __setitem__, __len__, __iter__(实现自定义集合)。
- 可调用对象:__call__(让对象像函数一样被调用)。
- 上下文管理:__enter__ 与 __exit__(with 语句的底层实现)。
生命周期
__init__很常见,就是初始化实例的。__new__见的不多,了解了一下,似乎就是创建实例时用于判断是否创建,在init之前执行判断。
所以为什么创建实例还需要一个判断呢?这主要是针对极少情况,单例模式,比如某个类只能有一个实例的情况。也就是无论你调用多少次这个类,最终也只有一个实例,常用于数据库连接池、配置读取类啥的。cls是指当前这个类本身。self是指当前实例本身。
class DatabaseConnection:
_instance = None
def __new__(cls, *args, **kwargs):
print("new")
if not cls._instance:
# 调用父类 object 的 __new__ 来创建对象
cls._instance = super().__new__(cls)
return cls._instance
def __init__(self, db_url):
print("init")
self.db_url = db_url
# 测试
db1 = DatabaseConnection("mysql://localhost")
db2 = DatabaseConnection("mysql://remote")
print(db1 is db2) # True,两者是同一个对象
# 输出
# new
# init
# new
# init
# True
字符串表示
这个很简单,就是可展示的标注。可以理解为这个对象的自我介绍。repr就是更详细一些,针对调试和开发,str就是可以粗糙直观一些,针对用户。
class User:
def __init__(self, name):
self.name = name
def __str__(self):
return f"用户:{self.name}"
def __repr__(self):
return f"User(name={self.name!r})"
u = User("张三")
print(u) # 调用 __str__
print(repr(u)) # 调用 __repr__
# 输出结果
# 用户:张三
# User(name='张三')
容器类
这个在深度学习数据处理那儿很常见,就是将类实现像列表/字典一样的操作。
# #
# __getitem__:通过 obj[key] 获取值。
# __setitem__:通过 obj[key] = value 设置值。
# __len__:len(obj)。
# __iter__:使对象可迭代。
class Bag:
def __init__(self, items):
self.items = items
def __len__(self):
return len(self.items)
def __getitem__(self, i):
return self.items[i]
def __setitem__(self, i, v):
self.items[i] = v
def __iter__(self):
return iter(self.items)
b = Bag([1,2,3])
print(len(b)) # 3
print(b[0]) # 1
b[1] = 9
for x in b: print(x) # 1 9 3
可调用对象
让一个类实例像函数一样被调用。编写复杂的装饰器或者状态保持。在后端中,常用于配置好的逻辑单元(如权限检查器)。感觉不是很常见。
class permissionchecker:
def __init__(self, role):
self.role = role
def __call__(self, user_role):
# 像函数一样被调用,执行逻辑
return self.role == user_role
# 预设一个“管理员检查器”
is_admin = permissionchecker("admin")
# 在后续逻辑中直接调用
if is_admin("guest"):
print("通过")
else:
print("拒绝") # 输出拒绝
上下文管理
配合with语句使用,自动处理资源的分配与释放。
这个也不常用……不过看起来在工程中似乎作用很大。举个例子,比如数据库连接,加入连接中出了问题,程序一般就抛出异常卡在那儿了,但是实际上无论连接是否异常,我们最后都应该有一步去执行关闭连接的操作,否则连接池被异常中断的占满,就卡死了。
先来了解下with,代码为例:
# 这是一个常规的连接,如果出错、波动,提前退出,那就永远不会执行close
conn = get_db_connection()
cursor = conn.cursor()
if not valid:
return # 提前退出,下面不会执行
cursor.execute(...)
conn.close() # 永远不会被调用
# 使用with之后
with get_db_connection() as conn:
cursor = conn.cursor()
cursor.execute(...)
# with语句等价于
conn = get_db_connection()
try:
cursor = conn.cursor()
cursor.execute(...)
finally:
conn.close() # 无论你中途 return、异常、崩溃,这行一定执行
OK,如果上述大概能看懂,我们在整个类结合with来看看:
class File:
def __enter__(self):
print("打开资源")
return self
def __exit__(self, exc_type, exc, tb):
print("释放资源")
with File() as f:
print("处理中")
# 这个with等价于
obj = File() # 1. 创建对象
f = obj.__enter__() # 2. 进入上下文
try:
print("处理中") # 3. 执行代码块
finally:
obj.__exit__(None, None, None) # 4. 无论如何都退出
with简直是极致的便捷!
至于__exit__里面三个参数是啥,def __exit__(self, exc_type, exc, tb),当 with 里面发生异常时,exit 会收到:exc_type:异常类型(ZeroDivisionError),exc:异常对象,tb:traceback。
说实话,这一个章节整理的微微有点乱,主要是都是个人平时不怎么用的东东,很多工具只有具体场景下才能体现,后续有机会会继续优化!不过从面试角度来看,似乎主要是一些概念性的,了解即可,重点可能在应用场景的理解上。