Python魔术方法 __getattribute__

发布于 2020-05-28  646 次阅读


在Python中,对于一个对象的属性访问,我们一般采用的是点(.)属性运算符进行操作。例如,有一个类实例对象foo,它有一个name属性,那便可以使用foo.name对此属性进行访问。一般而言,点(.)属性运算符比较直观,也是我们经常碰到的一种属性访问方式。然而,在点(.)属性运算符的背后却是别有洞天,值得我们对对象的属性访问进行探讨

该方法可以拦截对对象属性的所有访问企图,当属性被访问时,自动调用该方法(只适用于新式类,python3都是)。因此常用于实现一些访问某属性时执行一段代码的特性。

  • __getattribute__() 在查找真正要访问的属性之前就被调用了,无论该属性是否存在。
  • 使用__getattribute__()要特别注意,因为如果你在它里面不知何故(包括__dict__的访问)再次调用了__getattribute__(),你就会进入无穷递归。为了避免这种情况,如果你想要访问任何它需要的属性, 唯一安全的方式是使用基类(超类) 的方法__getattribute__(使用super)
  • 程序员主要使用__getattribute__()来实现某些拦截功能,特别是数据访问保护。例如下面这个例子将不允许访问对象的current元素:
class Count(object):

    default = 0

    def __init__(self, max, min):
        self.max = max
        self.min = min
        self.current = self.max + self.min

    # def __getattr__(self, item):
    #     self.__dict__[item] = 0
    #     return 0

    def __getattribute__(self, item):
        print('已被拦截-----------------------------------------', item)
        if item.startswith('cur'):
            raise AttributeError
        # return object.__getattribute__(self, item)
        return super().__getattribute__(item)


c = Count(1,10)
print(c.max)
print(c.min)
print(Count.default)
print(c.default)
print(Count.__dict__)
print(c.current)

###################################################################
已被拦截----------------------------------------- max
已被拦截----------------------------------------- min
已被拦截----------------------------------------- max
1
已被拦截----------------------------------------- min
10
0
已被拦截----------------------------------------- default
0
{'__module__': '__main__', 'default': 0, '__init__': <function Count.__init__ at 0x000000000282AA60>, '__getattribute__': <function Count.__getattribute__ at 0x000000000282AAE8>, '__dict__': <attribute '__dict__' of 'Count' objects>, '__weakref__': <attribute '__weakref__' of 'Count' objects>, '__doc__': None}
已被拦截----------------------------------------- current
Traceback (most recent call last):
  File "G:/Git/python_study/魔术方法/getattribute.py", line 37, in <module>
    print(c.current)
  File "G:/Git/python_study/魔术方法/getattribute.py", line 26, in __getattribute__
    raise AttributeError
AttributeError

Process finished with exit code 1

getattr()函数

  • getattr()函数是普通函数,它和特殊函数__getattr__()不是一回事
  • getattr()函数会在你试图读取一个不存在的属性时,引发AttributeError异常。

__getattr__()函数

  • __getattr__()函数是特殊函数
  • 它仅当属性不能在实例的__dict__或它的类,或者祖先类中找到时,才被调用。
  • 程序员主要用__getattr__()来实现类的灵活性,或者用它来做一些兜底的操作。
  • 绝大多数情况下,你需要的是__getattr__()

调用顺序

如果你的类中同时包含了__getattr__、__getattribute__时,如果__getattribute__抛出了AttributeError异常时该异常会被忽略,并且会继续调用__getattr__方法。如下

class Count(object):

    default = 0

    def __init__(self, max, min):
        self.max = max
        self.min = min
        self.current = self.max + self.min

    def __getattr__(self, item):
        self.__dict__[item] = 0
        return 0

    def __getattribute__(self, item):
        print('已被拦截-----------------------------------------', item)
        if item.startswith('cur'):
            raise AttributeError
        # return object.__getattribute__(self, item)
        return super().__getattribute__(item)


c = Count(1,10)
print(c.max)
print(c.min)
print(Count.default)
print(c.default)
print(Count.__dict__)
print(c.current)

##########################################################
已被拦截----------------------------------------- max
已被拦截----------------------------------------- min
已被拦截----------------------------------------- max
1
已被拦截----------------------------------------- min
10
0
已被拦截----------------------------------------- default
0
{'__module__': '__main__', 'default': 0, '__init__': <function Count.__init__ at 0x000000000280AA60>, '__getattr__': <function Count.__getattr__ at 0x000000000280AAE8>, '__getattribute__': <function Count.__getattribute__ at 0x000000000280AB70>, '__dict__': <attribute '__dict__' of 'Count' objects>, '__weakref__': <attribute '__weakref__' of 'Count' objects>, '__doc__': None}
已被拦截----------------------------------------- current
已被拦截----------------------------------------- __dict__
0

__setattr__

细心的你已经发现在,在对象c进行初始化时给属性max、min赋值时,触发了__getattribute__方法,其实是__setattr__方法触发的(Count中没有定义调用基类的__setattr__方法)

 __setattr__(self,key,value): 
1.拦截所有属性的赋值语句。
2.self.attr=value 相当于 self.__setattr__("attr",value)。
3.如果在__setattr__中对任何self属性赋值,都会再调用__setattr__,导致无穷递归循环。只能self.__dict__["attr"]=value 。 如下:

class Count(object):

    default = 0

    def __init__(self, max, min):
        self.max = max
        self.min = min
        self.current = self.max + self.min

    def __getattr__(self, item):
        if item == 'current':
            return 100

    def __setattr__(self, key, value):
        print("调用__setattr__", "key=", key)
        self.__dict__[key] = value

    def __getattribute__(self, item):
        print("已被拦截调用__getattribute__ ,", "item =", item)
        if item.startswith('cur'):
            raise AttributeError
        # return object.__getattribute__(self, item)
        return super().__getattribute__(item)


c = Count(1,10)
print(c.max)
print(c.min)
print(Count.default) # 通过类访问属性不会拦截
print(c.default)
print(Count.__dict__)
print(c.current)
##############################################################
调用__setattr__ key= max
已被拦截调用__getattribute__ , item = __dict__
调用__setattr__ key= min
已被拦截调用__getattribute__ , item = __dict__
已被拦截调用__getattribute__ , item = max
已被拦截调用__getattribute__ , item = min
调用__setattr__ key= current
已被拦截调用__getattribute__ , item = __dict__
已被拦截调用__getattribute__ , item = max
1
已被拦截调用__getattribute__ , item = min
10
0
已被拦截调用__getattribute__ , item = default
0
{'__module__': '__main__', 'default': 0, '__init__': <function Count.__init__ at 0x000000000265AA60>, '__getattr__': <function Count.__getattr__ at 0x000000000265AAE8>, '__setattr__': <function Count.__setattr__ at 0x000000000265AB70>, '__getattribute__': <function Count.__getattribute__ at 0x000000000265ABF8>, '__dict__': <attribute '__dict__' of 'Count' objects>, '__weakref__': <attribute '__weakref__' of 'Count' objects>, '__doc__': None}
已被拦截调用__getattribute__ , item = current
100

注意

  • 只要实现了__getattribute__方法,所有通过对象访问的属性(包括类属性)都会被拦截,而直接通过类访问属性则不会拦截。
  • 实现了__getattribute__方法,并没有主动抛出AttributeError异常,或者抛出其它类型的异常,都不会触发__getattr__

一名测试工作者,专注接口测试、自动化测试、性能测试、Python技术。