Как разрешение и вызов метода работает внутри Python? - PullRequest
6 голосов
/ 12 мая 2009

Как работает вызов методов в Python? Я имею в виду, как виртуальная машина Python интерпретирует это.

Это правда, что разрешение метода Python в Python может быть медленнее, чем в Java. Что такое поздний переплет?

Каковы различия в механизме отражения в этих двух языках? Где найти хорошие ресурсы, объясняющие эти аспекты?

Ответы [ 3 ]

8 голосов
/ 16 мая 2009

Вызов метода в 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. Это слишком сложно объяснить, поэтому, если вам интересно, посмотрите эту статью . Если вы не делаете какие-то действительно причудливые иерархии наследования (а вам не следует), достаточно знать, что порядок поиска слева направо, сначала глубина, и все подклассы класса ищутся до того, как этот класс будет найден.

4 голосов
/ 12 мая 2009

Имена (методы, функции, переменные) все решаются, глядя на пространство имен. Пространства имен реализованы в CPython как dict s (карты хешей).

Когда имя не найдено в пространстве имен экземпляра (dict), python выбирает класс, а затем базовые классы, следуя порядку разрешения метода (MRO).

Все решения выполняются во время выполнения.

Вы можете поиграть с модулем dis, чтобы увидеть, как это происходит в байт-коде.

Простой пример:

import dis
a = 1

class X(object):
    def method1(self):
        return 15

def test_namespace(b=None):
    x = X()
    x.method1()
    print a
    print b

dis.dis(test_namespace)

Это печатает:

  9           0 LOAD_GLOBAL              0 (X)
              3 CALL_FUNCTION            0
              6 STORE_FAST               1 (x)

 10           9 LOAD_FAST                1 (x)
             12 LOAD_ATTR                1 (method1)
             15 CALL_FUNCTION            0
             18 POP_TOP             

 11          19 LOAD_GLOBAL              2 (a)
             22 PRINT_ITEM          
             23 PRINT_NEWLINE       

 12          24 LOAD_FAST                0 (b)
             27 PRINT_ITEM          
             28 PRINT_NEWLINE       
             29 LOAD_CONST               0 (None)
             32 RETURN_VALUE        

Все LOAD являются поисками пространства имен.

1 голос
/ 12 мая 2009

Это правда, что метод python разрешение может быть медленнее в Python что на яве. Что такое поздний переплет?

Позднее связывание описывает стратегию того, как интерпретатор или компилятор определенного языка решает, как отобразить идентификатор на фрагмент кода. Например, рассмотрим запись obj.Foo() на C #. Когда вы компилируете это, компилятор пытается найти указанный объект и вставить ссылку на местоположение метода Foo, который будет вызываться во время выполнения. Все это разрешение метода происходит во время компиляции; мы говорим, что имена связаны "рано".

Напротив, Python связывает имена «поздно». Разрешение метода происходит в время выполнения : интерпретатор просто пытается найти ссылочный метод Foo с правильной сигнатурой, и если его там нет, возникает ошибка времени выполнения.

Каковы различия на механизм отражения в этих двух языки?

Динамические языки, как правило, имеют лучшие возможности отражения, чем статические языки, и Python очень мощен в этом отношении. Тем не менее, у Java есть довольно обширные способы проникновения во внутренности классов и методов. Тем не менее, вы не можете обойти многословие Java; вы будете писать гораздо больше кода, чтобы сделать то же самое в Java, чем в Python. См. java.lang.reflect API.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...