Вызов метода в Python состоит из двух отдельных этапов. Сначала выполняется поиск по атрибуту, а затем вызывается результат этого поиска. Это означает, что следующие два фрагмента имеют одинаковую семантику:
foo.bar()
method = foo.bar
method()
Поиск атрибутов в Python - довольно сложный процесс. Скажем, мы ищем атрибут с именем attr для объекта obj , что означает следующее выражение в коде Python: obj.attr
Сначала в словаре экземпляров obj выполняется поиск "attr", затем в словаре экземпляров класса obj и словари его родительских классов ищутся в порядке разрешения методов для "attr".
Обычно, если в экземпляре найдено значение, оно возвращается. Но если поиск в классе приводит к значению, которое имеет оба метода __get__ и __set__ (точнее, если поиск по словарю в классе значений и родительских классах имеет значения для обоих этих ключей), то атрибут класса рассматривается как нечто называется «дескриптор данных». Это означает, что метод __get__ для этого значения вызывается, передавая объект, для которого произошел поиск, и возвращается результат этого значения. Если атрибут класса не найден или не является дескриптором данных, возвращается значение из словаря экземпляров.
Если в словаре экземпляра нет значения, возвращается значение из поиска класса. Если это не «дескриптор без данных», то есть у него есть метод __get__. Затем вызывается метод __get__ и возвращается полученное значение.
Существует еще один особый случай, если obj является классом (экземпляр типа типа ), то значение экземпляра также проверяется, если это дескриптор и вызывается соответственно.
Если значение не найдено ни в экземпляре, ни в его иерархии классов, а класс 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 использует алгоритм C3 от Dylan. Это слишком сложно объяснить, поэтому, если вам интересно, посмотрите эту статью . Если вы не делаете какие-то действительно причудливые иерархии наследования (а вам не следует), достаточно знать, что порядок поиска слева направо, сначала глубина, и все подклассы класса ищутся до того, как этот класс будет найден.