属性与描述符的使用案例与__getattribute__

时间:2014-03-24 17:34:09

标签: python properties descriptor getattribute

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

在python中,您可以通过属性描述符魔术方法来控制属性的访问。哪个用例最具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

2 个答案:

答案 0 :(得分:17)

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

  • 如果普通属性有效,请不要使用property
  • 如果property可以使用,请不要编写自己的描述符。
  • 如果__getattr__可以使用,请不要使用property
  • 如果__getattribute__可用,请勿使用__getattr__

更具体地说明:使用属性来自定义一个或一小组属性的处理;使用__getattr__自定义所有属性的处理,或者除了一小组之外的所有属性;如果您希望使用__getattribute__,请使用__getattr__,但它不起作用;如果您正在做一些非常复杂的事情,请编写自己的描述符类。

如果您有一组或一小组要获取/设置的属性,则使用property。也就是说,您希望obj.propobj.prop = 2之类的东西秘密调用您编写的函数来自定义发生的事情。

如果您想要为那么多实际上并不想单独定义它们的属性执行此操作,则可以使用__getattr__,而是希望自定义整个属性访问过程。换句话说,不是挂钩obj.prop1obj.prop2等,而是你有很多想要能够挂钩到obj.<anything>并且通常处理它的人。 / p>

但是,__getattr__仍然不允许覆盖真正存在的属性所发生的事情,它只是让您可以使用全局处理来处理任何可能引发AttributeError的属性。使用__getattribute__可以挂钩来处理所有内容,甚至是可以在不弄乱__getattribute__的情况下工作的普通属性。因此,使用__getattribute__可能会破坏相当基本的行为,所以只有在考虑使用__getattr__时才应该使用它,但这还不够。它还可以产生明显的性能影响。例如,如果你要包装一个定义了某些属性的类,并且你希望能够以自定义方式包装这些属性,那么你可能需要使用__getattribute__,这样它们在某些情况下可以正常工作但是得到其他情况下的自定义行为。

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

在您的示例中,property将是最佳选择。如果您在if name == 'somespecificname'内进行__getattribute__,通常(并非总是)会出现红旗。如果您只需要专门处理一个特定名称,则可以在不弯腰到__getattribute__级别的情况下执行此操作。同样地,如果您为__get__编写的所有内容都是您在属性的getter方法中编写的内容,那么编写自己的描述符是没有意义的。

答案 1 :(得分:2)

__getattribute__是一个钩子,它使property(和其他描述符)首先工作,并在对象上调用所有属性访问。当属性甚至自定义描述符不足以满足您的需求时,请将其视为较低级别的API。如果没有通过其他方式找到属性,则__getattr__会调用__getattribute__作为后备。

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

当您需要将绑定任意对象添加到实例时,将使用描述符。当你需要用更先进的东西替换方法对象时;最近的一个示例是class-based decorator wrapping methods,它需要支持方法对象上的其他属性和方法。通常,当您仍在考虑标量属性时,您不需要描述符。