属性与描述符与 __getattribute__ 的用例

Posted

技术标签:

【中文标题】属性与描述符与 __getattribute__ 的用例【英文标题】:Use cases for property vs. descriptor vs. __getattribute__ 【发布时间】:2014-05-02 05:34:19 【问题描述】:

问题是指哪个更可取用于哪个用例,而不是技术背景。

在python中,您可以通过propertydescriptorma​​gic方法来控制属性的访问。在哪个用例中哪一个是最 Pythonic 的?它们似乎都具有相同的效果(请参见下面的示例)。

我正在寻找类似的答案:

属性:应该在……的情况下使用 描述符:在……的情况下,应该使用它而不是属性。 魔术方法:仅在……时使用。

示例

一个用例是一个可能无法在__init__ 方法中设置的属性,例如,因为该对象尚未出现在数据库中,但稍后会出现。每次访问该属性时,都应该尝试设置并返回。

作为在 Python shell 中使用 Copy&Paste 的示例,有一个类希望仅在第二次被要求时才显示其属性。那么,哪一种是最好的方法,或者有不同的情况,其中一种更可取?以下是实现它的三种方式:

带有属性::

class ContactBook(object):
    intents = 0

    def __init__(self):
        self.__first_person = None

    def get_first_person(self):
        ContactBook.intents += 1
        if self.__first_person is None:
            if ContactBook.intents > 1:
                value = 'Mr. First'
                self.__first_person = value
            else:
                return None
        return self.__first_person

    def set_first_person(self, value):
        self.__first_person = value

    first_person = property(get_first_person, set_first_person)

__getattribute__::

class ContactBook(object):
    intents = 0

    def __init__(self):
        self.first_person = None

    def __getattribute__(self, name):
        if name == 'first_person' \
                and object.__getattribute__(self, name) is None:
            ContactBook.intents += 1
            if ContactBook.intents > 1:
                value = 'Mr. First'
                self.first_person = value
            else:
                value = None
        else:
            value = object.__getattribute__(self, name)
        return value

描述符::

class FirstPerson(object):
    def __init__(self, value=None):
        self.value = None

    def __get__(self, instance, owner):
        if self.value is None:
            ContactBook.intents += 1
            if ContactBook.intents > 1:
                self.value = 'Mr. First'
            else:
                return None
        return self.value


class ContactBook(object):
    intents = 0
    first_person = FirstPerson()

每一个都有这个行为::

book = ContactBook()
print(book.first_person)
# >>None
print(book.first_person)
# >>Mr. First

【问题讨论】:

__getattribute__总是调用,__getattr__ 只有在没有通过其他方式找到属性时才会被调用。 __getattribute__property(和其他描述符对象)工作的机制。 那是 descriptor 不是 decorator @Eric:谢谢,我编辑了问题。 请注意,@property@first_person.setterdecorator 形式更符合 Python 风格 【参考方案1】:

基本上,尽可能使用最简单的一种。粗略地说,复杂性/繁重性的顺序是:常规属性、property__getattr____getattribute__/descriptor。 (__getattribute__ 和自定义描述符都是您可能不需要经常做的事情。)这导致了一些简单的经验法则:

如果正常属性可以使用,请不要使用 property。 如果property 可以使用,请不要编写自己的描述符。 如果property 可以使用,请不要使用__getattr__。 如果__getattr__ 可以使用,请不要使用__getattribute__

说得更具体一点:使用属性来自定义处理一个或一小组属性;使用__getattr__ 自定义处理所有属性,或除一小部分之外的所有属性;如果您希望使用__getattr__,请使用__getattribute__,但这并不完全有效;如果您正在做一些非常复杂的事情,请编写自己的描述符类。

当您有一个或一小组属性要挂钩其获取/设置时,您可以使用property。也就是说,您希望 obj.propobj.prop = 2 之类的东西秘密调用您编写的函数来自定义发生的情况。

当您想要对如此多的属性执行此操作时,您将使用__getattr__,您实际上并不想单独定义它们,而是想要将整个属性访问过程作为一个整体进行自定义。换句话说,与其挂钩到obj.prop1obj.prop2 等,您有很多东西希望能够挂钩到obj.<anything>,并在一般情况下处理它。

但是,__getattr__ 仍然不会让您覆盖真正存在的属性所发生的事情,它只是让您对任何会引发 AttributeError 的属性的使用进行全面处理。使用__getattribute__ 可以让您处理所有事情,即使是正常的属性也可以在不与__getattribute__ 混淆的情况下工作。正因为如此,使用__getattribute__ 有可能破坏相当基本的行为,所以你应该只在你考虑使用__getattr__ 时才使用它,但这还不够。它还可以产生显着的性能影响。例如,您可能需要使用__getattribute__,如果您要包装定义某些属性的类,并且希望能够以自定义方式包装这些属性,以便它们在某些情况下照常工作但获得自定义行为在其他情况下。

最后,我想说编写自己的描述符是一项相当高级的任务。 property 是一个描述符,对于大约 95% 的情况,它是您唯一需要的。 here 给出了为什么要编写自己的描述符的一个很好的简单示例:基本上,如果您必须编写几个具有类似行为的 propertys,您可能会这样做;描述符可让您分解常见行为以避免代码重复。例如,自定义描述符用于驱动 Django 和 SQLAlchemy 等系统。如果您发现自己在编写如此复杂的内容,则可能需要编写自定义描述符。

在您的示例中,property 将是最佳选择。如果您在 __getattribute__ 内执行 if name == 'somespecificname',通常(并非总是)是一个危险信号。如果您只需要专门处理一个特定的名称,您可能可以做到这一点,而不会屈服于__getattribute__ 的水平。同样,如果您为 __get__ 编写的所有内容都是您可以在属性的 getter 方法中编写的,那么编写您自己的描述符也没有意义。

【讨论】:

在使用__getattribute__ 覆盖之前,我可能会使用描述符对象。我很少需要使用__getattribute__ 挂钩。 :-) 也许,是的。我认为__getattribute__ 和描述符都非常罕见,很难说哪个在复杂性上是第一位的。 我经常使用描述符;将类上定义的对象作为属性访问时绑定到实例是一个很好的功能。 @BrenBarn:非常感谢。在 Python 中,当我不清楚类似的实现时,我总是对自己产生怀疑,因为我认为对于每种情况都有一种最好的方法来做到这一点。你让我对我的问题所涉及的三种可能方式恢复了信心。很好的答案,谢谢。 @Iodnas __getattribute__ 应该是最后的选择,因为它是属性访问引擎——它是调用 __getattr__ 和描述符的东西。【参考方案2】:

__getattribute__ 是使property(和其他描述符)首先工作的钩子,并被调用用于对象上的 all 属性访问。当属性甚至自定义描述符不足以满足您的需求时,可以将其视为较低级别的 API。 __getattr____getattribute__ 在没有通过其他方式定位到属性时调用,作为后备。

property 用于具有固定名称的动态属性,__getattr__ 用于更动态的属性(例如,一系列以算法方式映射到值的属性)。

当您需要将任意对象绑定到一个实例时使用描述符。例如,当您需要用更高级的东西替换方法对象时;最近的一个例子是class-based decorator wrapping methods,它需要支持方法对象上的其他属性和方法。一般来说,当你还在考虑标量属性时,你不需要描述符。

【讨论】:

以上是关于属性与描述符与 __getattribute__ 的用例的主要内容,如果未能解决你的问题,请参考以下文章

python之属性描述符与属性查找规则

Python-魔法函数__getattr__()与__getattribute__()的区别

python - getattr 与 getattribute 机制

__getattribute__

Python的__getattr__和__getattribute__

飘逸的python - __get__ vs __getattr__ vs __getattribute__以及属性的搜索策略