属性与描述符与 __getattribute__ 的用例
Posted
技术标签:
【中文标题】属性与描述符与 __getattribute__ 的用例【英文标题】:Use cases for property vs. descriptor vs. __getattribute__ 【发布时间】:2014-05-02 05:34:19 【问题描述】:问题是指哪个更可取用于哪个用例,而不是技术背景。
在python中,您可以通过property、descriptor或magic方法来控制属性的访问。在哪个用例中哪一个是最 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.setter
的 decorator 形式更符合 Python 风格
【参考方案1】:
基本上,尽可能使用最简单的一种。粗略地说,复杂性/繁重性的顺序是:常规属性、property
、__getattr__
、__getattribute__
/descriptor。 (__getattribute__
和自定义描述符都是您可能不需要经常做的事情。)这导致了一些简单的经验法则:
property
。
如果property
可以使用,请不要编写自己的描述符。
如果property
可以使用,请不要使用__getattr__
。
如果__getattr__
可以使用,请不要使用__getattribute__
。
说得更具体一点:使用属性来自定义处理一个或一小组属性;使用__getattr__
自定义处理所有属性,或除一小部分之外的所有属性;如果您希望使用__getattr__
,请使用__getattribute__
,但这并不完全有效;如果您正在做一些非常复杂的事情,请编写自己的描述符类。
当您有一个或一小组属性要挂钩其获取/设置时,您可以使用property
。也就是说,您希望 obj.prop
和 obj.prop = 2
之类的东西秘密调用您编写的函数来自定义发生的情况。
当您想要对如此多的属性执行此操作时,您将使用__getattr__
,您实际上并不想单独定义它们,而是想要将整个属性访问过程作为一个整体进行自定义。换句话说,与其挂钩到obj.prop1
和obj.prop2
等,您有很多东西希望能够挂钩到obj.<anything>
,并在一般情况下处理它。
但是,__getattr__
仍然不会让您覆盖真正存在的属性所发生的事情,它只是让您对任何会引发 AttributeError 的属性的使用进行全面处理。使用__getattribute__
可以让您处理所有事情,即使是正常的属性也可以在不与__getattribute__
混淆的情况下工作。正因为如此,使用__getattribute__
有可能破坏相当基本的行为,所以你应该只在你考虑使用__getattr__
时才使用它,但这还不够。它还可以产生显着的性能影响。例如,您可能需要使用__getattribute__
,如果您要包装定义某些属性的类,并且希望能够以自定义方式包装这些属性,以便它们在某些情况下照常工作但获得自定义行为在其他情况下。
最后,我想说编写自己的描述符是一项相当高级的任务。 property
是一个描述符,对于大约 95% 的情况,它是您唯一需要的。 here 给出了为什么要编写自己的描述符的一个很好的简单示例:基本上,如果您必须编写几个具有类似行为的 property
s,您可能会这样做;描述符可让您分解常见行为以避免代码重复。例如,自定义描述符用于驱动 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-魔法函数__getattr__()与__getattribute__()的区别
python - getattr 与 getattribute 机制
Python的__getattr__和__getattribute__
飘逸的python - __get__ vs __getattr__ vs __getattribute__以及属性的搜索策略