Python高级技术之:`Python`的`Proxy`模式:`__getattr__`和`__getattribute__`的实现。

各位观众,各位朋友,大家好!我是你们的老朋友,今天咱们来聊聊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}")

代码解释:

  1. RealSubject:这是我们的目标对象,它有一个do_something方法。
  2. Proxy:这是代理对象,它接收一个RealSubject实例作为参数。
  3. __getattr__:当访问proxy对象不存在的属性时,它会被调用。
  4. __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}")

代码解释:

  1. MyClass:包含一些属性,包括公有属性x,保护属性_y和私有属性__z。 还有一个logger实例。
  2. __getattribute__:每次访问MyClass对象的属性时,它都会被调用。
  3. 重要:__getattribute__中,我们使用super().__getattribute__(name)来获取属性的值。这是为了避免无限递归。如果直接使用self.name,会导致__getattribute__被再次调用,从而陷入无限循环。
  4. 我们添加了一个日志记录功能,每次访问属性时都会记录一条日志。
  5. 我们使用try...except块来处理AttributeError异常。如果属性不存在,我们会记录一条错误日志,然后重新抛出异常。

重点:

  • __getattribute__会在每次访问属性时都被调用。
  • 使用super().__getattribute__(name)来获取属性的值,避免无限递归。
  • __getattribute__可以用于实现更复杂的代理模式,比如访问控制、日志记录和缓存。

四、__getattr__ vs __getattribute__: 区别和选择

特性 __getattr__ __getattribute__
调用时机 仅在访问不存在的属性时调用 每次访问属性时都调用
使用场景 处理不存在的属性,实现属性的动态生成或委托 实现更复杂的代理模式,如访问控制、日志记录、缓存
风险 相对较低 容易导致无限递归,需要小心使用
性能 仅在访问不存在的属性时调用,对性能影响较小 每次访问属性都调用,对性能有一定影响

如何选择?

  • 如果你的目标只是处理不存在的属性,那么__getattr__是更好的选择。
  • 如果你的目标是实现更复杂的代理模式,比如访问控制、日志记录或缓存,那么__getattribute__可能更适合你。但一定要小心使用,避免无限递归。

五、Proxy模式的应用场景

Proxy模式在实际开发中有很多应用场景,下面列举几个常见的例子:

  1. 延迟初始化:

    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()
  2. 访问控制:

    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.
  3. 日志记录:

    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__的用法。 记住,实践是检验真理的唯一标准,多写代码,多尝试,才能真正掌握这些知识。

今天的讲座就到这里,感谢大家的观看!咱们下期再见!

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注