各位观众,各位朋友,大家好!我是你们的老朋友,今天咱们来聊聊Python里一个有点意思的东西——Proxy模式,以及实现它的一大利器:__getattr__
和__getattribute__
。
先别被这些名字吓跑,其实它们就像是Python世界里的“中间人”或者“代理”,能帮你巧妙地处理对象属性的访问。准备好了吗?咱们这就开始!
一、什么是Proxy模式?(别跟我说你是第一次听说)
想象一下,你想买演唱会门票,但是官方渠道太难抢了,于是你找了个黄牛,让他帮你搞定。这个黄牛就是个Proxy,他替你和官方售票系统打交道。
在编程世界里,Proxy模式也是类似的概念。它允许你创建一个代理对象,控制对另一个对象的访问。这个代理对象可以执行一些额外的操作,比如:
- 延迟初始化: 只有在真正需要的时候才创建目标对象。
- 访问控制: 限制对目标对象某些属性的访问。
- 日志记录: 记录对目标对象属性的访问情况。
- 缓存: 缓存目标对象属性的值,避免重复计算。
总之,Proxy模式就像一个“中间人”,可以在访问目标对象之前或之后做一些“手脚”,从而增强或改变原有的行为。
二、__getattr__
:属性不存在的时候才找我
__getattr__
是一个特殊的Python方法,当尝试访问一个对象不存在的属性时,它会被调用。注意,是“不存在”的属性!如果属性已经存在,Python会直接返回它的值,不会理会__getattr__
。
你可以把__getattr__
想象成一个“失物招领处”,只有当你找不到东西的时候,才会去那里碰碰运气。
代码示例:
class RealSubject:
def do_something(self):
print("RealSubject: Doing something important.")
class Proxy:
def __init__(self, real_subject):
self._real_subject = real_subject
def __getattr__(self, name):
print(f"Proxy: Someone is trying to access attribute '{name}' that doesn't exist directly in Proxy.")
try:
return getattr(self._real_subject, name) # 委托给RealSubject
except AttributeError:
print(f"Proxy: RealSubject doesn't have attribute '{name}' either.")
raise # 重要: 重新抛出异常,否则会隐藏错误!
# 使用
real_subject = RealSubject()
proxy = Proxy(real_subject)
proxy.do_something() # 直接调用, RealSubject 的方法
try:
proxy.do_something_else() # 访问不存在的属性
except AttributeError as e:
print(f"Caught the exception: {e}")
代码解释:
RealSubject
:这是我们的目标对象,它有一个do_something
方法。Proxy
:这是代理对象,它接收一个RealSubject
实例作为参数。__getattr__
:当访问proxy
对象不存在的属性时,它会被调用。- 在
__getattr__
中,我们尝试将属性访问委托给RealSubject
对象。如果RealSubject
对象也没有这个属性,我们会重新抛出AttributeError
异常,这样可以避免隐藏错误。
重点:
__getattr__
只在访问不存在的属性时才会被调用。- 在
__getattr__
中,你需要决定如何处理这种情况。你可以委托给其他对象,返回默认值,或者抛出异常。 - 一定要记得重新抛出
AttributeError
异常,否则会隐藏错误。
三、__getattribute__
:属性访问的“门卫”
__getattribute__
比__getattr__
更强大,也更复杂。它会在每次访问对象的属性时都被调用,无论属性是否存在。你可以把__getattribute__
想象成一个“门卫”,每次有人想进入你的房子,都必须经过他的允许。
注意: 使用__getattribute__
要非常小心,因为它会影响所有属性的访问,包括内部属性。如果使用不当,可能会导致无限递归或者其他奇怪的问题。
代码示例:
class Logger:
def log(self, message):
print(f"LOG: {message}")
class MyClass:
def __init__(self):
self.x = 10
self._y = 20 # Protected attribute
self.__z = 30 # Private attribute (name mangled)
self._logger = Logger()
def __getattribute__(self, name):
print(f"__getattribute__ called for attribute: {name}")
# 重要:避免无限递归!
if name == '_logger':
return super().__getattribute__(name)
self._logger.log(f"Attempting to access attribute: {name}")
try:
return super().__getattribute__(name)
except AttributeError as e:
self._logger.log(f"AttributeError: {e}")
raise
def my_method(self):
return self.x + self._y
# 使用
my_object = MyClass()
print(my_object.x)
print(my_object.my_method())
try:
print(my_object.__z) # 会触发 AttributeError, 因为Python的名字改编
except AttributeError as e:
print(f"Caught: {e}")
代码解释:
MyClass
:包含一些属性,包括公有属性x
,保护属性_y
和私有属性__z
。 还有一个logger实例。__getattribute__
:每次访问MyClass
对象的属性时,它都会被调用。- 重要: 在
__getattribute__
中,我们使用super().__getattribute__(name)
来获取属性的值。这是为了避免无限递归。如果直接使用self.name
,会导致__getattribute__
被再次调用,从而陷入无限循环。 - 我们添加了一个日志记录功能,每次访问属性时都会记录一条日志。
- 我们使用
try...except
块来处理AttributeError
异常。如果属性不存在,我们会记录一条错误日志,然后重新抛出异常。
重点:
__getattribute__
会在每次访问属性时都被调用。- 使用
super().__getattribute__(name)
来获取属性的值,避免无限递归。 __getattribute__
可以用于实现更复杂的代理模式,比如访问控制、日志记录和缓存。
四、__getattr__
vs __getattribute__
: 区别和选择
特性 | __getattr__ |
__getattribute__ |
---|---|---|
调用时机 | 仅在访问不存在的属性时调用 | 每次访问属性时都调用 |
使用场景 | 处理不存在的属性,实现属性的动态生成或委托 | 实现更复杂的代理模式,如访问控制、日志记录、缓存 |
风险 | 相对较低 | 容易导致无限递归,需要小心使用 |
性能 | 仅在访问不存在的属性时调用,对性能影响较小 | 每次访问属性都调用,对性能有一定影响 |
如何选择?
- 如果你的目标只是处理不存在的属性,那么
__getattr__
是更好的选择。 - 如果你的目标是实现更复杂的代理模式,比如访问控制、日志记录或缓存,那么
__getattribute__
可能更适合你。但一定要小心使用,避免无限递归。
五、Proxy模式的应用场景
Proxy模式在实际开发中有很多应用场景,下面列举几个常见的例子:
-
延迟初始化:
class HeavyObject: def __init__(self): print("HeavyObject: Initializing...") # 模拟耗时操作 import time time.sleep(2) print("HeavyObject: Initialization complete.") def process(self): print("HeavyObject: Processing data.") class LazyProxy: def __init__(self): self._real_object = None def __getattr__(self, name): if self._real_object is None: self._real_object = HeavyObject() return getattr(self._real_object, name) # 使用 proxy = LazyProxy() # 只有在第一次访问process方法时,才会创建HeavyObject实例 proxy.process()
-
访问控制:
class SensitiveData: def __init__(self): self._data = "Sensitive information" def get_data(self): return self._data class AccessControlProxy: def __init__(self, real_subject, user_role): self._real_subject = real_subject self._user_role = user_role def get_data(self): if self._user_role == "admin": return self._real_subject.get_data() else: return "Access denied." # 使用 sensitive_data = SensitiveData() admin_proxy = AccessControlProxy(sensitive_data, "admin") user_proxy = AccessControlProxy(sensitive_data, "user") print(admin_proxy.get_data()) # 输出: Sensitive information print(user_proxy.get_data()) # 输出: Access denied.
-
日志记录:
class RealService: def do_something(self, data): print(f"RealService: Processing data: {data}") return f"Result: {data}" class LoggingProxy: def __init__(self, real_subject): self._real_subject = real_subject def do_something(self, data): print(f"LoggingProxy: Before calling do_something with data: {data}") result = self._real_subject.do_something(data) print(f"LoggingProxy: After calling do_something, result: {result}") return result # 使用 real_service = RealService() logging_proxy = LoggingProxy(real_service) logging_proxy.do_something("Important data")
六、总结
Proxy模式是一种非常有用的设计模式,它可以帮助你控制对对象的访问,实现延迟初始化、访问控制、日志记录等功能。__getattr__
和__getattribute__
是实现Proxy模式的两个重要工具。
__getattr__
只在访问不存在的属性时才会被调用,适合处理属性的动态生成或委托。__getattribute__
会在每次访问属性时都被调用,适合实现更复杂的代理模式。但要注意避免无限递归。
希望今天的讲解能够帮助你更好地理解Proxy模式和__getattr__
、__getattribute__
的用法。 记住,实践是检验真理的唯一标准,多写代码,多尝试,才能真正掌握这些知识。
今天的讲座就到这里,感谢大家的观看!咱们下期再见!