这里的重点是,在 Python 中,一切(包括函数、方法、“属性”——或任何其他描述符——类甚至模块)都是一个对象,并且“数据”和“函数或方法”没有不同的命名空间。IOW,在 Python 中,一个对象具有属性、句点 - 没有“成员数据”或“成员函数”。甚至基类也是属性(它们本身也是对象,所以它们也有属性)。
属性查找规则是(非常简化 - 我不会提及插槽等一些特殊情况):
阅读:
- 首先在父类中查找该名称的属性。如果找到并且该属性实现了描述符协议的“get”部分,则调用该属性的
__get__
方法。
- 然后在实例的
__dict__
- 然后如果类(或父类之一)有一个
__getattr__
方法,调用它
- 然后引发 AttributeError 异常
用于设置:
- 首先在父类中查找该名称的属性。如果找到并且该属性实现了描述符协议的“设置”部分,则调用该属性的
__set__
方法。
- 然后将属性存储在实例的
__dict__
我提到但没有解释描述符协议。这个协议(如果你来自 Java,协议是一种“隐含的”接口——你不必声明它,只需实现它)说如果一个对象有一个__get__
方法,最终有一个__set__
方法(这里我再次通过不提及该部分而过度简化__del__
)AND是一个类属性,那么当在类的实例上查找时,它的方法将__get__
在“读取”查找时被调用(以实例和类作为参数)并且它的__set__
方法将被调用(使用实例和值)在“写”。
IOW,描述符协议是 Python 中计算属性的基础。
现在关于property
类型(是的,它是一个类,而不是一个函数):它确实以一种非常简单的方式实现了描述符协议。下面是如何在纯 Python 中实现属性类型的简化版本(不考虑该__del__
部分):
class property(object):
def __init__(self, fget, fset=None, doc=None):
self.fget = fget
self.fset = fset
self.doc = doc
def __get__(self, instance, cls=None):
if not instance:
return self
return self.fget(instance)
def __set__(self, instance, value):
if not self.fset:
raise AttributeError("attribute is read-only")
self.fset(instance, value)
回到问题 - 给定类声明:
class Something(object):
def __init__(self,val):
self.a=val
def get(self):
return self.a
def set(self,val):
self.a=val
a=property(get,set)
我们有一个Something
具有 (class) 属性的类对象,该属性a
是具有fget=Something.get
和的属性fset=Something.set
。现在当我们实例化时会发生什么Something
?初始化器使用val
参数调用,并尝试绑定self.a
到该参数。属性查找规则(对于“写作”——我们应该说是“绑定”)开始起作用,并注意到Something
类对象有一个属性a
——作为property
类型的实例——实现__set__
了协议描述符的一部分。所以查找规则调用Something.a.__set__(theinstance, val)
,解析为Something.a.fset(theinstance, val)
,实现为self.a = val
。新属性查找,找到a
实现描述符协议绑定部分的类属性,调用它,等等……,砰,无限递归。
长话短说:属性是属性是属性;)
请注意,在您的第三个示例中,该set
方法尝试设置self._a
而不是self.a
. 由于该类没有名为 的描述符_a
,因此只需使用该名称创建一个实例属性,因此这里没有递归。
有关描述符协议的更多信息,请参见 - http://docs.python.org/reference/datamodel.html#implementing-descriptors
- http://wiki.python.org/moin/ComputedAttributesUsingPropertyObjects
如果您想了解 Python 的“方法”到底是什么(提示:function
类型实现了描述符协议)以及为什么需要“自我”参数,您也可以阅读以下内容:- http://wiki.python.org/ moin/FromFunctionToMethod