属性、描述符与__getattribute__的用途场景
这个问题主要是想知道在什么情况下使用哪种方式更好,而不是讨论技术背景。
在Python中,你可以通过属性、描述符或者魔法方法来控制属性的访问。哪种方式在特定情况下更符合Python的风格呢?它们似乎都能达到相似的效果(下面有例子)。
我希望得到这样的回答:
- 属性:在……的情况下应该使用。
- 描述符:在……的情况下应该用描述符而不是属性。
- 魔法方法:只有在……的情况下才使用。
例子
一个使用场景是,有一个属性可能在__init__
方法中无法设置,比如因为对象还没有在数据库中存在,但在之后的某个时候可以设置。每次访问这个属性时,都应该尝试设置并返回它。
举个例子,有一个类希望在第二次请求这个属性时才展示它。那么,哪种方式最好,或者在不同情况下哪种方式更合适呢?以下是三种实现方式:
使用属性::
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 个回答
__getattribute__
是一个特殊的功能,它让 property
(还有其他描述符)能够正常工作。每当你访问一个对象的属性时,这个功能都会被调用。可以把它看作是一个比较底层的接口,当 property
或者自定义描述符不能满足你的需求时,就可以用这个。
当你需要动态属性且名字是固定的时候,使用 property
;而如果属性的性质更动态,比如一系列属性与某些值之间有算法关系,那么就用 __getattr__
。
描述符的用途在于你需要把一些任意的对象绑定到一个实例上。比如说,当你需要用更复杂的东西来替代方法对象时;最近的一个例子是一个 基于类的装饰器,它需要支持方法对象上的额外属性和方法。一般来说,如果你还在考虑简单的属性,就不需要用到描述符。
简单来说,尽量使用最简单的方法。大致上,复杂程度的顺序是:普通属性、property
、__getattr__
、__getattribute__
/描述符。(__getattribute__
和自定义描述符通常不需要经常使用。) 这就形成了一些简单的经验法则:
- 如果普通属性可以用,就不要用
property
。 - 如果
property
可以用,就不要自己写描述符。 - 如果
property
可以用,就不要用__getattr__
。 - 如果
__getattr__
可以用,就不要用__getattribute__
。
更具体地说:当你想定制处理一个或一小部分属性时,使用property
;当你想定制处理所有属性,或者除了少数几个属性外的所有属性时,使用__getattr__
;如果你希望使用__getattr__
但它不太适用,就使用__getattribute__
;如果你在做一些非常复杂的事情,就写自己的描述符类。
当你有一个或一小部分属性需要特别处理时,就使用property
。也就是说,你希望像obj.prop
和obj.prop = 2
这样的操作背后,调用你写的函数来定制发生的事情。
当你想对很多属性进行这样的处理,而不想一个一个定义时,就使用__getattr__
。换句话说,你希望能够处理obj.<任何属性名>
,而不是单独处理obj.prop1
、obj.prop2
等。
不过,__getattr__
并不能让你覆盖那些确实存在的属性的行为,它只是让你能对那些本来会引发AttributeError的属性进行统一处理。使用__getattribute__
可以让你处理所有属性,甚至是那些本来不需要干预的普通属性。由于这个原因,使用__getattribute__
可能会破坏一些基本行为,所以只有在考虑过使用__getattr__
但不够时,才应该使用它。它也可能会对性能产生明显影响。例如,如果你在包装一个定义了一些属性的类,并且希望以自定义的方式包装这些属性,以便在某些情况下正常工作,而在其他情况下则有自定义行为,你可能需要使用__getattribute__
。
最后,我想说,编写自己的描述符是一项相对高级的任务。property
就是一个描述符,在大约95%的情况下,它是你唯一需要的。一个简单的例子是,如果你需要写多个具有相似行为的property
,那么写一个描述符可以让你提取出共同的行为,避免代码重复。自定义描述符在像Django和SQLAlchemy这样的系统中被使用。如果你发现自己在写这种复杂度的东西,可能需要写一个自定义描述符。
在你的例子中,property
是最佳选择。如果你在__getattribute__
中写if name == 'somespecificname'
,通常(但不是总是)是个红旗。如果你只需要特别处理一个特定的名字,可能不需要使用__getattribute__
。同样,如果你在__get__
中写的内容只是你可以在property
的getter方法中写的,那就没有必要自己写描述符。