你问的是 Python 中的内置函数 setattr
, getattr
, hasattr
, delattr
,以及对应的魔法方法 __setattr__
, __getattr__
, __delattr__
,再加上类似操作字典的魔法方法 __setitem__
, __getitem__
, __delitem__
的用法和区别。下面帮你系统总结讲清楚:
一、内置函数 vs 魔法方法区别
函数/方法 | 作用 | 用于对象属性还是容器元素 |
---|---|---|
setattr(obj, name, value) | 设置对象 obj 的属性 name 为 value | 对象属性 |
getattr(obj, name[, default]) | 获取对象 obj 的属性 name ,找不到时可返回 default | 对象属性 |
hasattr(obj, name) | 判断对象 obj 是否有属性 name | 对象属性 |
delattr(obj, name) | 删除对象 obj 的属性 name | 对象属性 |
obj.__setattr__(name, value) | 魔法方法,重载属性赋值时触发 | 对象属性 |
obj.__getattr__(name) | 魔法方法,访问不存在属性时触发 | 对象属性(访问未定义属性的回退方法) |
obj.__delattr__(name) | 魔法方法,删除属性时触发 | 对象属性 |
obj.__setitem__(key, value) | 魔法方法,重载索引赋值(如 obj[key] = value ) | 容器元素(字典、列表等的索引操作) |
obj.__getitem__(key) | 魔法方法,重载索引访问(如 obj[key] ) | 容器元素 |
obj.__delitem__(key) | 魔法方法,重载索引删除(如 del obj[key] ) | 容器元素 |
二、内置函数详细用法
class MyClass:
pass
obj = MyClass()
# setattr(obj, attr_name, value) 等同于 obj.attr_name = value
setattr(obj, 'x', 10)
print(obj.x) # 10
# getattr(obj, attr_name) 等同于 obj.attr_name,带默认值避免异常
print(getattr(obj, 'x')) # 10
print(getattr(obj, 'y', 'default')) # default
# hasattr(obj, attr_name) 判断属性是否存在
print(hasattr(obj, 'x')) # True
print(hasattr(obj, 'y')) # False
# delattr(obj, attr_name) 等同于 del obj.attr_name
delattr(obj, 'x')
print(hasattr(obj, 'x')) # False
三、魔法方法详细用法和区别
1. __setattr__(self, name, value)
- 任何通过
obj.attr = value
的赋值都会调用它。 - 如果你重写它,通常要调用
super().__setattr__(name, value)
或self.__dict__[name] = value
否则会导致无限递归。
示例:
class MyClass:
def __setattr__(self, name, value):
print(f'Setting {name} to {value}')
super().__setattr__(name, value)
obj = MyClass()
obj.x = 5 # 输出: Setting x to 5
2. __getattr__(self, name)
- 只在访问不存在的属性时调用。
- 不是属性访问的通用钩子(那是
__getattribute__
)。 - 常用来动态返回属性或实现代理。
示例:
class MyClass:
def __getattr__(self, name):
print(f'{name} not found, return 42')
return 42
obj = MyClass()
print(obj.x) # 输出: x not found, return 42 然后返回 42
3. __delattr__(self, name)
- 任何
del obj.attr
操作会调用它。
示例:
class MyClass:
def __delattr__(self, name):
print(f'Deleting {name}')
super().__delattr__(name)
obj = MyClass()
obj.x = 5
del obj.x # 输出: Deleting x
四、__setitem__
, __getitem__
, __delitem__
用法
- 用于重载容器的索引访问操作,如
obj[key] = value
,obj[key]
,del obj[key]
- 多用于自定义字典、列表等容器类型
示例:
class MyDict:
def __init__(self):
self.data = {}
def __setitem__(self, key, value):
print(f'Setting key {key} to {value}')
self.data[key] = value
def __getitem__(self, key):
print(f'Getting key {key}')
return self.data[key]
def __delitem__(self, key):
print(f'Deleting key {key}')
del self.data[key]
d = MyDict()
d['a'] = 10 # Setting key a to 10
print(d['a']) # Getting key a \n 10
del d['a'] # Deleting key a
五、总结对比
场景 | 使用示例 | 触发方式 | 作用域 |
---|---|---|---|
设置属性 | obj.attr = val | 触发 __setattr__ 或 setattr() | 对象属性 |
访问属性 | obj.attr | 触发 __getattr__ / __getattribute__ 或 getattr() | 对象属性 |
删除属性 | del obj.attr | 触发 __delattr__ 或 delattr() | 对象属性 |
访问容器元素(索引) | obj[key] | 触发 __getitem__ | 容器索引(字典/列表等) |
设置容器元素(索引) | obj[key] = val | 触发 __setitem__ | 容器索引 |
删除容器元素(索引) | del obj[key] | 触发 __delitem__ | 容器索引 |
如果你需要,我还可以帮你写一份深度的示例代码,或帮你解读 __getattribute__
和 __missing__
等高级用法,告诉我!
好的!下面给你一份更深度的示例,包含:
__setattr__
、__getattr__
、__getattribute__
、__delattr__
的详细示范,展示它们调用顺序和区别- 自定义容器的
__setitem__
、__getitem__
、__delitem__
示例 - 以及特殊方法
__missing__
(字典找不到键时调用)
1. 属性访问相关魔法方法演示
class Demo:
def __init__(self):
self.x = 10
def __setattr__(self, name, value):
print(f"__setattr__ called: {name} = {value}")
super().__setattr__(name, value) # 避免递归
def __getattr__(self, name):
print(f"__getattr__ called for missing attribute: {name}")
return f"default_for_{name}"
def __getattribute__(self, name):
print(f"__getattribute__ called: {name}")
# 始终调用父类的 __getattribute__ 避免死循环
return super().__getattribute__(name)
def __delattr__(self, name):
print(f"__delattr__ called: {name}")
super().__delattr__(name)
obj = Demo()
print(obj.x) # 触发 __getattribute__,访问已有属性
print(obj.y) # 触发 __getattribute__ 后找不到,再触发 __getattr__
obj.z = 100 # 触发 __setattr__
del obj.x # 触发 __delattr__
输出示例:
__setattr__ called: x = 10
__getattribute__ called: x
10
__getattribute__ called: y
__getattr__ called for missing attribute: y
default_for_y
__setattr__ called: z = 100
__delattr__ called: x
2. 自定义容器示例:字典样式访问
class MyDict:
def __init__(self):
self.data = {}
def __setitem__(self, key, value):
print(f"__setitem__: Setting key {key} = {value}")
self.data[key] = value
def __getitem__(self, key):
print(f"__getitem__: Getting key {key}")
return self.data[key]
def __delitem__(self, key):
print(f"__delitem__: Deleting key {key}")
del self.data[key]
def __missing__(self, key):
print(f"__missing__: Key {key} not found")
return f"default_for_{key}"
d = MyDict()
d['a'] = 1
print(d['a'])
try:
print(d['b']) # 会触发 KeyError,没有自动调用 __missing__
except KeyError:
print("KeyError caught")
# Python内置dict的__missing__示例:
class MyDictWithMissing(dict):
def __missing__(self, key):
print(f"__missing__: Key {key} not found in dict")
return f"default_for_{key}"
d2 = MyDictWithMissing()
d2['a'] = 10
print(d2['a'])
print(d2['b']) # 调用 __missing__ 返回默认值
3. 总结
方法 | 调用场景 | 备注 |
---|---|---|
__setattr__ | obj.attr = val | 设置属性时调用,重载赋值操作 |
__getattribute__ | 任何访问属性都会调用 | 用于全局捕获访问,需避免递归调用 |
__getattr__ | 访问不存在的属性时调用 | 作为找不到属性的“补救”手段 |
__delattr__ | del obj.attr | 删除属性时调用 |
__setitem__ | obj[key] = val | 容器设置元素 |
__getitem__ | obj[key] | 容器访问元素 |
__delitem__ | del obj[key] | 容器删除元素 |
__missing__ | 字典找不到键时调用(内置dict) | 只对内置 dict 有效,提供默认值 |
明白了!我帮你写一个动态代理类示例,演示如何用 __getattr__
、__setattr__
、__getitem__
、__setitem__
等魔法方法,实现一个“万能代理对象”,能动态代理属性和字典键的访问与设置,还示范懒加载和日志功能。
动态代理类示例(属性与字典键通吃)
class DynamicProxy:
def __init__(self, target=None):
# 真实数据存放处(属性和键值都存在这里)
super().__setattr__('_data', {})
# 也可以传入一个初始目标对象,用于代理
super().__setattr__('_target', target)
# 属性赋值代理
def __setattr__(self, name, value):
print(f"[__setattr__] Setting attribute '{name}' = {value}")
self._data[name] = value
# 属性访问代理
def __getattr__(self, name):
print(f"[__getattr__] Getting attribute '{name}'")
if name in self._data:
return self._data[name]
# 模拟懒加载
if self._target:
print(f"Delegating to target for '{name}'")
return getattr(self._target, name)
raise AttributeError(f"'{type(self).__name__}' object has no attribute '{name}'")
# 删除属性代理
def __delattr__(self, name):
print(f"[__delattr__] Deleting attribute '{name}'")
if name in self._data:
del self._data[name]
else:
raise AttributeError(f"'{type(self).__name__}' object has no attribute '{name}'")
# 键值赋值代理
def __setitem__(self, key, value):
print(f"[__setitem__] Setting key '{key}' = {value}")
self._data[key] = value
# 键值访问代理
def __getitem__(self, key):
print(f"[__getitem__] Getting key '{key}'")
if key in self._data:
return self._data[key]
if self._target and hasattr(self._target, '__getitem__'):
print(f"Delegating to target for key '{key}'")
return self._target[key]
raise KeyError(key)
# 键值删除代理
def __delitem__(self, key):
print(f"[__delitem__] Deleting key '{key}'")
if key in self._data:
del self._data[key]
else:
raise KeyError(key)
# 示例用法
proxy = DynamicProxy()
# 属性赋值和访问
proxy.name = "Alice"
print(proxy.name)
# 字典键赋值和访问
proxy['age'] = 30
print(proxy['age'])
# 删除属性
del proxy.name
try:
print(proxy.name)
except AttributeError as e:
print(e)
# 删除键值
del proxy['age']
try:
print(proxy['age'])
except KeyError as e:
print(e)
# 演示代理另一个对象
class RealObj:
def __init__(self):
self.existing = "I exist"
def __getitem__(self, key):
return f"Value for {key} from RealObj"
real = RealObj()
proxy2 = DynamicProxy(real)
print(proxy2.existing) # 通过 __getattr__ 委托
print(proxy2['somekey']) # 通过 __getitem__ 委托
说明
__setattr__
和__getattr__
实现属性动态代理和日志__setitem__
和__getitem__
实现字典键代理和日志- 支持懒加载(访问不存在的属性时代理到目标对象)
- 支持删除属性和键
- 这是实现 ORM、RPC、远程代理、缓存代理等模式的基础
明白!下面给你示范一个带异步调用代理和属性动态计算缓存的高级动态代理类示例。它支持:
- 属性/方法的异步调用自动转发
- 计算属性缓存(第一次计算后存储结果,下次直接用缓存)
- 异步方法支持用
async def
编写的目标方法 - 同时支持普通属性和字典键访问
高级动态代理类(异步 + 缓存)
import asyncio
import inspect
class AsyncDynamicProxy:
def __init__(self, target=None):
super().__setattr__('_data', {}) # 存普通属性/缓存
super().__setattr__('_target', target) # 代理目标对象
super().__setattr__('_cache', {}) # 缓存计算属性
def __setattr__(self, name, value):
print(f"[__setattr__] Setting attribute '{name}' = {value}")
self._data[name] = value
def __getattr__(self, name):
print(f"[__getattr__] Getting attribute '{name}'")
# 优先返回缓存值
if name in self._cache:
print(f"Return cached value for '{name}'")
return self._cache[name]
# 查找普通存储值
if name in self._data:
return self._data[name]
# 代理目标属性(方法或属性)
if self._target:
attr = getattr(self._target, name)
# 如果是协程函数,返回包装异步调用的函数
if inspect.iscoroutinefunction(attr):
async def async_wrapper(*args, **kwargs):
print(f"Async proxy calling method '{name}'")
result = await attr(*args, **kwargs)
# 缓存结果(可选)
self._cache[name] = result
return result
return async_wrapper
# 如果是普通函数,返回同步包装函数
if callable(attr):
def sync_wrapper(*args, **kwargs):
print(f"Sync proxy calling method '{name}'")
result = attr(*args, **kwargs)
# 缓存结果(可选)
self._cache[name] = result
return result
return sync_wrapper
# 普通属性直接返回
self._cache[name] = attr # 缓存属性值
return attr
raise AttributeError(f"'{type(self).__name__}' has no attribute '{name}'")
def __delattr__(self, name):
print(f"[__delattr__] Deleting attribute '{name}'")
self._data.pop(name, None)
self._cache.pop(name, None)
def __setitem__(self, key, value):
print(f"[__setitem__] Setting key '{key}' = {value}")
self._data[key] = value
def __getitem__(self, key):
print(f"[__getitem__] Getting key '{key}'")
if key in self._data:
return self._data[key]
if self._target and hasattr(self._target, '__getitem__'):
return self._target[key]
raise KeyError(key)
def __delitem__(self, key):
print(f"[__delitem__] Deleting key '{key}'")
if key in self._data:
del self._data[key]
else:
raise KeyError(key)
# 测试用的异步目标对象
class RealAsyncObj:
def __init__(self):
self.value = 42
async def async_method(self, x):
print(f"RealAsyncObj.async_method called with {x}")
await asyncio.sleep(1)
return x * 2
def sync_method(self, y):
print(f"RealAsyncObj.sync_method called with {y}")
return y + 10
async def main():
real = RealAsyncObj()
proxy = AsyncDynamicProxy(real)
print(proxy.value) # 访问属性
# 调用同步方法
print(proxy.sync_method(5))
# 调用异步方法
result = await proxy.async_method(7)
print(f"Async method result: {result}")
# 再调用,测试缓存(这里演示缓存作用)
print(proxy.value) # 直接从缓存取
print(proxy.sync_method(5)) # 缓存结果未清除,直接返回缓存
# 设置新属性
proxy.new_attr = 'hello'
print(proxy.new_attr)
asyncio.run(main())
说明
- 访问代理的普通属性、方法时自动缓存结果,减少重复计算
- 对异步方法用
async_wrapper
包装,调用时自动await
,符合异步语义 - 同步方法用同步包装,自动打印调用日志
- 支持通过
__setattr__
动态设置属性,自动写入内部数据字典 - 通过
_cache
字典缓存计算结果,你可以根据需求自行控制缓存更新策略
明白!下面给你示范一个更完善的动态代理类,支持:
- 缓存失效策略(基于时间,自动过期)
- 异步属性懒加载(访问时异步计算并缓存结果)
- 结合
__call__
支持函数/方法代理
代码示例
import asyncio
import inspect
import time
from functools import wraps
class AdvancedAsyncProxy:
def __init__(self, target=None, cache_expire=5):
super().__setattr__('_data', {}) # 普通属性存储
super().__setattr__('_target', target) # 目标对象
super().__setattr__('_cache', {}) # 缓存数据 {key: (value, expire_timestamp)}
super().__setattr__('_cache_expire', cache_expire) # 缓存过期时间秒
def _is_cache_valid(self, key):
if key not in self._cache:
return False
value, expire = self._cache[key]
return expire is None or expire > time.time()
def _set_cache(self, key, value):
expire = time.time() + self._cache_expire if self._cache_expire else None
self._cache[key] = (value, expire)
def __setattr__(self, name, value):
print(f"[__setattr__] Setting attribute '{name}' = {value}")
self._data[name] = value
self._cache.pop(name, None) # 设置新值,清除缓存
def __getattr__(self, name):
print(f"[__getattr__] Getting attribute '{name}'")
if self._is_cache_valid(name):
print(f"Return cached value for '{name}'")
return self._cache[name][0]
if name in self._data:
self._set_cache(name, self._data[name])
return self._data[name]
if self._target:
attr = getattr(self._target, name)
# 支持异步属性懒加载(异步函数)
if inspect.iscoroutinefunction(attr):
async def async_wrapper(*args, **kwargs):
print(f"Async proxy calling method '{name}'")
result = await attr(*args, **kwargs)
self._set_cache(name, result)
return result
self._set_cache(name, async_wrapper)
return async_wrapper
if callable(attr):
@wraps(attr)
def sync_wrapper(*args, **kwargs):
print(f"Sync proxy calling method '{name}'")
result = attr(*args, **kwargs)
self._set_cache(name, result)
return result
self._set_cache(name, sync_wrapper)
return sync_wrapper
# 普通属性缓存
self._set_cache(name, attr)
return attr
raise AttributeError(f"'{type(self).__name__}' has no attribute '{name}'")
def __delattr__(self, name):
print(f"[__delattr__] Deleting attribute '{name}'")
self._data.pop(name, None)
self._cache.pop(name, None)
def __setitem__(self, key, value):
print(f"[__setitem__] Setting key '{key}' = {value}")
self._data[key] = value
self._cache.pop(key, None)
def __getitem__(self, key):
print(f"[__getitem__] Getting key '{key}'")
if self._is_cache_valid(key):
return self._cache[key][0]
if key in self._data:
self._set_cache(key, self._data[key])
return self._data[key]
if self._target and hasattr(self._target, '__getitem__'):
return self._target[key]
raise KeyError(key)
def __delitem__(self, key):
print(f"[__delitem__] Deleting key '{key}'")
self._data.pop(key, None)
self._cache.pop(key, None)
# 支持函数/方法直接调用代理
def __call__(self, *args, **kwargs):
print(f"[__call__] Called with args={args}, kwargs={kwargs}")
if callable(self._target):
result = self._target(*args, **kwargs)
# 简单缓存调用结果,过期时间内复用
self._set_cache('__call__', result)
return result
raise TypeError(f"'{type(self).__name__}' object is not callable")
# 测试用目标对象
class TestObj:
def __init__(self):
self.val = 100
async def async_double(self, x):
print("TestObj.async_double running...")
await asyncio.sleep(1)
return x * 2
def sync_add(self, y):
print("TestObj.sync_add running...")
return y + 10
def __call__(self, z):
print("TestObj.__call__ running...")
return z * z
async def test():
obj = TestObj()
proxy = AdvancedAsyncProxy(obj, cache_expire=3)
print(proxy.val) # 访问缓存或目标属性
print(proxy.val) # 走缓存
print(proxy.sync_add(5)) # 调用同步方法
print(proxy.sync_add(5)) # 走缓存
result = await proxy.async_double(7) # 调用异步方法
print(f"Async double result: {result}")
result = await proxy.async_double(7) # 走缓存(这里缓存的是 async_wrapper,没再调用目标)
print(proxy(4)) # 调用 __call__ 代理
print(proxy(4)) # 走缓存
# 等待缓存过期
print("Waiting for cache to expire...")
await asyncio.sleep(4)
# 缓存过期后重新调用
print(proxy.val)
print(proxy.sync_add(5))
result = await proxy.async_double(7)
print(f"Async double after cache expired: {result}")
asyncio.run(test())
核心功能点说明
- 缓存机制:
_cache
字典存储值和过期时间,访问时检查是否有效 - 缓存失效:超过
cache_expire
秒自动失效,重新调用目标方法或访问目标属性 - 异步懒加载:异步函数调用时自动
await
,并缓存返回的协程函数包装器 __call__
支持:代理目标可被当函数调用,结果缓存- 同步方法:包装并缓存返回值
- 缓存清除:属性或键被重新赋值时,清除缓存
需要我帮你把它封装成可配置的库,或者做更复杂的调用链跟踪/日志系统,随时告诉我!
发表回复