zoukankan      html  css  js  c++  java
  • 再谈python的属性拦截

    __setattr__是python重载协议中的内容,用于在实例中设置属性。

    实际使用方式:

    class Old(object):
        def __init__(self):
            pass
        
        def __setattr__(self, key, value):
            print "__setattr__: ", key, value
            object.__setattr__(self, key, value)
        
        def __getattribute__(self, key):
            print "__getattribute__: ", key
            return object.__getattribute__(self, key)
        
        def __getattr__(self, key):
            print "run __getattr__ in undefinied param: ", key
            return object.__getattribute__(self, key)
    
    o = Old()
    o.ok = 123
    print o.ok
    print o.undef

    运行结果:

    __setattr__:  ok 123 
    
    __getattribute__:  ok  
    123
    
    __getattribute__:  undef
    
    run __getattr__ in undefinied param:  undef
    
    Traceback (most recent call last):
      File "dict_object.py", line 36, in <module>
        print o.undef
      File "dict_object.py", line 31, in __getattr__
        return object.__getattribute__(self, key)
    AttributeError: 'Old' object has no attribute 'undef'

    o.undef 是一个为定义的实例属性,会被__getattr__捕捉到,但是在实例上没有这个属性,最后还是会报错。

    但是如果我们现在的需求是这样,预先在实例的__init__函数上上设置一个变量,例如:self.data = {}

    我们让接下来的__setattr__和__getattribute__或__getattr__的存取属性都在self.data的字典中进行,如果还是使用上面的代码,将会带来极大的麻烦。

    因为我们首先要取得实例的data属性,即self.data,但是这个动作又必须经过__getattr__或__getattribute__操作,可能会这样写:

    class Old(object):
        def __init__(self):
            self.data = {}
        
        def __setattr__(self, key, value):
            print "__setattr__: ", key, value
            data = object.__getattribute__(self, 'data')
            data[key] = value
        
        def __getattribute__(self, key):
            print "__getattribute__: ", key
            return object.__getattribute__(self, key)
        
        def __getattr__(self, key):
            print "run __getattr__ in undefinied param: ", key
            return object.__getattribute__(self, key)
    
    o = Old()
    o.ok = 123
    print dir(o)

    运行结果:

    __setattr__:  data {}
    Traceback (most recent call last):
      File "dict_object.py", line 35, in <module>
        o = Old()
      File "dict_object.py", line 19, in __init__
        self.data = {}
      File "dict_object.py", line 23, in __setattr__
        data = object.__getattribute__(self, 'data')
    AttributeError: 'Old' object has no attribute 'data'

    我们甚至连self.data都没有赋值成功! 为什么呢? 还是因为这一句:

    data = object.__getattribute__(self, 'data')

    因为__init__里面赋值data给self要经过__getattribute__的拦截,但是此时实例上还没有data属性,上面一行代码当然会报属性错误!

    OK,我们再加上异常捕获怎么样? 尝试忽略这个异常,让我们试试:(不过在尝试之前,大家可能想到结果了)

    class Old(object):
        def __init__(self):
            self.data = {}
        
        def __setattr__(self, key, value):
            print "__setattr__: ", key, value
            try:
                data = object.__getattribute__(self, 'data')
                data[key] = value
            except:
                pass
        
        def __getattribute__(self, key):
            print "__getattribute__: ", key
            return object.__getattribute__(self, key)
        
        def __getattr__(self, key):
            print "run __getattr__ in undefinied param: ", key
            return object.__getattribute__(self, key)
    
    o = Old()
    o.ok = 123
    print dir(o)

    运行结果:

    __setattr__:  data {}
    __setattr__:  ok 123
    __getattribute__:  __dict__
    __getattribute__:  __members__
    run __getattr__ in undefinied param:  __members__
    __getattribute__:  __methods__
    run __getattr__ in undefinied param:  __methods__
    __getattribute__:  __class__
    ['__class__', '__delattr__', '__dict__', '__doc__', '__format__', '__getattr__', '__getattribute__', '__hash__', '__init__', '__module__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__']

    OK,运行成功,代码没有报错。中间淡色的部分可以忽略,它是dir函数获取实例上属性的过程。 注意加粗的部分,实例上根本没有data这个属性!

    所以这个方法行不通。

    那咋办捏?

    有一个办法,在《Python学习手册》第4版中,提到了用实例的__dict__命名空间来预先分配好实例的属性:

    class Old(object):
        def __init__(self):
            self.__dict__['data'] = {}
        
        def __setattr__(self, key, value):
            print "__setattr__: ", key, value
            #data = object.__getattribute__(self, 'data')
            #data[key] = value
            # 或者直接这样:
            self.data[key] = value
        
        def __getattribute__(self, key):
            print "__getattribute__: ", key
            return object.__getattribute__(self, key)
        
        def __getattr__(self, key):
            print "run __getattr__ in undefinied param: ", key
            return object.__getattribute__(self, key)
    
    o = Old()
    o.ok = 123
    print o.data

    OK,这样没问题了。

    如果我们既想简单模拟类似dict类的工作方式,又想实现以属性的方式访问,我们可以直接重载__getitem__和__setitem__和__delitem__方法,并使用上面的代码:

    class Old(object):
        def __init__(self):
            self.__dict__['data'] = {}
        
        def __setattr__(self, key, value):
            self.data[key] = value
        
        def __getattr__(self, key):
            # 这里我们用了一个取巧的方法
            # 因为self.__dict__还是会被__getattribute__拦截到,导致无限递归
            # 所以不能用它来实现
            return self.data[key]
        
        def __setitem__(self, key, value):
            self.data[key] = value
        
        def __getitem__(self, key):
            try:
                return self.data[key]
            except IndexError:
                pass
        
        def __delitem__(self, key):
            del self.data[key]
    
    o = Old()
    o.first = 123
    print o.first
    
    o['second'] = 456
    print o['second']

    特别说明:

    在上面的代码中,属性访问使用了__getattr__,虽然只有在实例未定义该属性时才会被__getattr__拦截。

    但是因为我们的__setattr__已经把属性保存在了self.data里面,所以每次__getattr__都会被调用,算是一种投机取巧的方式。

    最终的原因还是因为,self.__dict__也会被__getattribute__拦截,所以不能在__getattribute__中访问self.__dict。

  • 相关阅读:
    浅析MySQL二进制日志
    MySQL升级
    浅析MySQL复制
    MySQL关于exists的一个bug
    TokuDB存储引擎
    MySQL中RESET SLAVE和RESET MASTER的区别
    MySQL半同步复制
    MySQL线程池
    分析MariaDB初始化脚本mysql_install_db
    Python装饰器
  • 原文地址:https://www.cnblogs.com/huazi/p/2802080.html
Copyright © 2011-2022 走看看