Python 中的方法调用由两个不同的可分离步骤组成。首先完成属性查找,然后调用该查找的结果。这意味着以下两个 sn-ps 具有相同的语义:
foo.bar()
method = foo.bar
method()
Python 中的属性查找是一个相当复杂的过程。假设我们正在查找对象 obj 上名为 attr 的属性,这意味着 Python 代码中的以下表达式:obj.attr
首先在obj的实例字典中查找“attr”,然后在方法中查找obj类的实例字典及其父类的字典“attr”的解析顺序。
通常,如果在实例上找到值,则返回该值。但是,如果对类的查找产生一个同时具有 __get__ 和 __set__ 方法的值(准确地说,如果对值类和父类的字典查找具有这两个键的值),那么类属性被视为某种东西称为“数据描述符”。这意味着调用该值的 __get__ 方法,传入发生查找的对象并返回该值的结果。如果类属性未找到或不是数据描述符,则返回实例字典中的值。
如果实例字典中没有值,则返回类查找中的值。除非它恰好是“非数据描述符”,即它具有 __get__ 方法。然后调用 __get__ 方法并返回结果值。
还有一种特殊情况,如果 obj 恰好是一个类,(type 类型的实例),那么还会检查实例值是否它是一个描述符并被相应地调用。
如果在实例及其类层次结构中没有找到值,并且 obj 的类具有 __getattr__ 方法,则调用该方法。
以下显示了用 Python 编码的算法,有效地完成了 getattr() 函数的工作。 (不包括任何漏掉的错误)
NotFound = object() # A singleton to signify not found values
def lookup_attribute(obj, attr):
class_attr_value = lookup_attr_on_class(obj, attr)
if is_data_descriptor(class_attr_value):
return invoke_descriptor(class_attr_value, obj, obj.__class__)
if attr in obj.__dict__:
instance_attr_value = obj.__dict__[attr]
if isinstance(obj, type) and is_descriptor(instance_attr_value):
return invoke_descriptor(instance_attr_value, None, obj)
return instance_attr_value
if class_attr_value is NotFound:
getattr_method = lookup_attr_on_class(obj, '__getattr__')
if getattr_method is NotFound:
raise AttributeError()
return getattr_method(obj, attr)
if is_descriptor(class_attr_value):
return invoke_descriptor(class_attr_value, obj, obj.__class__)
return class_attr_value
def lookup_attr_on_class(obj, attr):
for parent_class in obj.__class__.__mro__:
if attr in parent_class.__dict__:
return parent_class.__dict__[attr]
return NotFound
def is_descriptor(obj):
if lookup_attr_on_class(obj, '__get__') is NotFound:
return False
return True
def is_data_descriptor(obj):
if not is_descriptor(obj) or lookup_attr_on_class(obj, '__set__') is NotFound :
return False
return True
def invoke_descriptor(descriptor, obj, cls):
descriptormethod = lookup_attr_on_class(descriptor, '__get__')
return descriptormethod(descriptor, obj, cls)
你问的方法调用与所有这些描述符废话有什么关系?问题是,函数也是对象,它们恰好实现了描述符协议。如果属性查找在类上找到一个函数对象,它的 __get__ 方法被调用并返回一个“绑定方法”对象。绑定方法只是函数对象的一个小包装器,它存储查找函数的对象,并在调用时将该对象添加到参数列表(通常用于方法的函数 self 论据是)。
这里有一些说明性代码:
class Function(object):
def __get__(self, obj, cls):
return BoundMethod(obj, cls, self.func)
# Init and call added so that it would work as a function
# decorator if you'd like to experiment with it yourself
def __init__(self, the_actual_implementation):
self.func = the_actual_implementation
def __call__(self, *args, **kwargs):
return self.func(*args, **kwargs)
class BoundMethod(object):
def __init__(self, obj, cls, func):
self.obj, self.cls, self.func = obj, cls, func
def __call__(self, *args, **kwargs):
if self.obj is not None:
return self.func(self.obj, *args, **kwargs)
elif isinstance(args[0], self.cls):
return self.func(*args, **kwargs)
raise TypeError("Unbound method expects an instance of %s as first arg" % self.cls)
对于方法解析顺序(在 Python 中实际上是指属性解析顺序),Python 使用 Dylan 的 C3 算法。这里解释得太复杂了,有兴趣的可以看this article。除非你正在做一些非常时髦的继承层次结构(你不应该这样做),否则知道查找顺序是从左到右,深度优先,并且在搜索该类之前搜索一个类的所有子类就足够了。