Python使用技巧--__getattr__和__getattribute__的比较

__getattr__和__getattribute__是python中的运算符重载方法,它们提供了拦截类实例的属性获取。

两者的区别:

  • __getattr__针对未定义的属性运行(那些没有在一个实例上出现的属性,以及没有从它的任何类继承的属性)。
  • __getattribute__针对所有的属性运行。因为它是涵盖一切的,所以在使用它的时候,必须小心避免通过把属性访问传递给父类而导致递归循环(它必须把获取指向一个父类已跳过自身)。

还有__getattr__在python2.x和python3.x都可用,但是__getattribute__则对于python2.x中的新式类以及python3.x中的所有类(默认都是新式类)可用。

示例:

使用__getattr__

# -*-coding:utf-8-*-

class Person:
    def __init__(self, name):
        self._name = name

    def __getattr__(self, attr):
        print("get:" + attr)
        if attr == "name":
            return self._name
        else:
            raise AttributeError(attr)

    def __setattr__(self, attr, value):
        print("set:" + attr)
        if attr == "name":
            attr = "_name"
        self.__dict__[attr] = value # 不可以直接self.attr = value,会造成循环

    def __delattr__(self, attr):
        print("del:" + attr)
        if attr == "name":
            attr = "_name"
        del self.__dict__[attr]

bob = Person("Bob Smith")
print(bob.name)
bob.name = "Robert Smith"
print(bob.name)
del bob.name

#输出
set:_name
get:name
Bob Smith
set:name
get:name
Robert Smith
del:name

注意:这里的__setattr__属性赋值为什么不直接使用self.attr=value,反而使用__dict__命名空间字典的一个键赋值,是为了避免循环。同理__delattr__也是相同的一个道理。

使用__getattribute__

要采用__getattribute__实现相同的结果,必须通过把新的获取传递到父类来避免递归循环:

如果使用以下代码,则发生递归循环:

# -*-coding:utf-8-*-

class Person:
    def __init__(self, name):
        self._name = name

    def __getattribute__(self, attr):
        print("get:" + attr)
        if attr == "name":
            attr = "_name"
        return self._name
bob = Person("Bob Smith")
print(bob.name)

报错输出:

get:_name
get:_name
get:_name
.....
get:_name
get:_name
  
Traceback (most recent call last):
  File "/Users/edwin/PycharmProjects/testProject/test.py", line 27, in <module>
    print(bob.name)
  File "/Users/edwin/PycharmProjects/testProject/test.py", line 11, in __getattribute__
    return self._name
  File "/Users/edwin/PycharmProjects/testProject/test.py", line 11, in __getattribute__
    return self._name
  File "/Users/edwin/PycharmProjects/testProject/test.py", line 11, in __getattribute__
    return self._name
  [Previous line repeated 993 more times]
  File "/Users/edwin/PycharmProjects/testProject/test.py", line 8, in __getattribute__
    print("get:" + attr)
RecursionError: maximum recursion depth exceeded while calling a Python object

可以用下面代码替换上面实例中的__getattr__

    def __getattribute__(self, attr):
        print("get:" + attr)
        if attr == "name":
            attr = "_name"
        # return self._name # 不可以直接返回self._name,会造成递归循环
        return object.__getattribute__(self, attr)  # 避免递归循环

修改后的代码输出:

set:_name
get:__dict__
get:name
Bob Smith
set:name
get:__dict__
get:name
Robert Smith
del:name
get:__dict__

输出是有点区别的,因为在__setattr__和__delattr__中的获取中触发了一次额外的__getattribute___调用(此处是因为获取__dict__本身会再次触发__getattribute__)。所以这里建议__setattr__和__delattr__也可以把自己的属性赋值或者属性删除传递给一个更高的父类而避免循环,就像__getattribute__一样。

    def __setattr__(self, attr, value):
        print("set:" + attr)
        if attr == "name":
            attr = "_name"
        object.__setattr__(self, attr, value)
    def __delattr__(self, attr):
        print("del:" + attr)
        if attr == "name":
            attr = "_name"
        object.__delattr__(self, attr)

总结:当使用__setattr__,__delattr__时,为了避免循环:可使用命名空间字典操作或者父类方法调用,而且根据前面所述,推荐使用父类方法调用),但是,通过上面的解释,__getattribute__不能使用命名空间字典操作来避免循环(因为获取__dict__本身会再次触发__getattribute__)。

优化后的实例代码:

# -*-coding:utf-8-*-

class Person:
    def __init__(self, name):
        self._name = name

    def __getattribute__(self, attr):
        print("get:" + attr)
        if attr == "name":
            attr = "_name"
        # return self._name # 不可以直接返回self._name,会造成递归循环
        return object.__getattribute__(self, attr)  # 避免递归循环

    def __setattr__(self, attr, value):
        print("set:" + attr)
        if attr == "name":
            attr = "_name"
        object.__setattr__(self, attr, value)

    def __delattr__(self, attr):
        print("del:" + attr)
        if attr == "name":
            attr = "_name"
        object.__delattr__(self, attr)

posted on 2021-10-07 17:53  xufat  阅读(697)  评论(0)    收藏  举报

导航

/* 返回顶部代码 */ TOP