Подтвердить что ты не робот

Как в Python работают расширенные классы (Monkey Patching)?

class Foo(object):
  pass

foo = Foo()
def bar(self):
  print 'bar'

Foo.bar = bar
foo.bar() #bar

Исходя из JavaScript, если прототип класса был дополнен определенным атрибутом. Известно, что все экземпляры этого "класса" будут иметь этот атрибут в своей цепочке прототипов, поэтому никакие изменения не должны выполняться ни по одному из его экземпляров или "подклассов".

В этом смысле, как язык на основе класса, такой как Python, достигнет исправления Monkey?

4b9b3361

Ответ 1

Реальный вопрос: как это может быть? В Python классы являются первоклассными объектами в своем собственном праве. Доступ к атрибутам в экземплярах класса разрешен путем поиска атрибутов в экземпляре, а затем класса, а затем родительских классов (в порядке разрешения метода). Эти запросы выполняются во время выполнения (как и все в Python). Если вы добавите атрибут в класс после создания экземпляра, экземпляр все равно "увидит" новый атрибут просто потому, что ничего не мешает ему.

Другими словами, это работает, потому что Python не кэширует атрибуты (если только ваш код не делает), потому что он не использует отрицательные кеширования или теневые классы или какие-либо методы оптимизации, которые могли бы его подавить (или, когда реализации Python, они учитывают, что класс может измениться) и потому, что все это время выполнения.

Ответ 2

Я просто прочитал кучу документации, и насколько я могу судить, вся история о том, как foo.bar разрешена, выглядит следующим образом:

  • Можно ли найти foo.__getattribute__ следующим процессом? Если да, используйте результат foo.__getattribute__('bar').
    • (Поиск __getattribute__ не приведет к бесконечной рекурсии, но реализация этого может быть.)
    • (В действительности мы всегда найдем __getattribute__ в объектах нового стиля, поскольку реализация по умолчанию представлена ​​в object - но эта реализация имеет следующий процесс.;))
    • (Если мы определим метод __getattribute__ в Foo и получим доступ к foo.__getattribute__, foo.__getattribute__('__getattribute__') будет! Но это означает, что не подразумевает бесконечное рекурсия - если вы осторожны;))
  • Является bar "специальным" именем для атрибута, предоставленного средой исполнения Python (например, __dict__, __class__, __bases__, __mro__)? Если это так, используйте это. (Насколько я могу судить, __getattribute__ попадает в эту категорию, что позволяет избежать бесконечной рекурсии.)
  • Есть ли bar в foo.__dict__ dict? Если это так, используйте foo.__dict__['bar'].
  • Существует ли foo.__mro__ (т.е. Foo фактически класс)? Если так,
    • Для каждого базового класса base в foo.__mro__ [1:]:
      • (Обратите внимание, что первая будет самой Foo, которую мы уже искали.)
      • Является ли bar в base.__dict__? Если так:
        • Пусть x be base.__dict__['bar'].
        • Можно ли найти (опять-таки, рекурсивно, но это не вызовет проблемы) x.__get__?
          • Если это так, используйте x.__get__(foo, foo.__class__).
          • (Обратите внимание, что функция bar - это сам объект, а компилятор Python автоматически предоставляет функции атрибуту __get__, который предназначен для использования таким образом.)
          • В противном случае используйте x.
  • Для каждого базового класса base of foo.__class__.__mro__:
    • (Обратите внимание, что эта рекурсия не является проблемой: эти атрибуты должны всегда существовать и попадать в "предоставленный оператором времени Python". foo.__class__.__mro__[0] всегда будет foo.__class__, т.е. Foo в нашем примере. )
    • (Обратите внимание, что мы делаем это, даже если foo.__mro__ существует. Это потому, что классы тоже имеют класс: его имя type, и оно обеспечивает, среди прочего, метод, используемый для вычисления атрибутов __mro__ в первую очередь.)
    • Является ли bar в base.__dict__? Если так:
      • Пусть x be base.__dict__['bar'].
      • Можно ли найти (опять-таки, рекурсивно, но это не вызовет проблемы) x.__get__?
        • Если это так, используйте x.__get__(foo, foo.__class__).
        • (Обратите внимание, что функция bar - это сам объект, а компилятор Python автоматически предоставляет функции атрибуту __get__, который предназначен для использования таким образом.)
        • В противном случае используйте x.
  • Если мы еще не нашли что-то для использования: можем ли мы найти foo.__getattr__ предыдущим процессом? Если это так, используйте результат foo.__getattr__('bar').
  • Если все не удалось, raise AttributeError.

bar.__get__ на самом деле не является функцией - это "метод-обертка", но вы можете себе представить, что это выполняется смутно:

# Somewhere in the Python internals
class __method_wrapper(object):
    def __init__(self, func):
        self.func = func
    def __call__(self, obj, cls):
        return lambda *args, **kwargs: func(obj, *args, **kwargs)
        # Except it actually returns a "bound method" object
        # that uses cls for its __repr__
    # and there is a __repr__ for the method_wrapper that I *think*
    # uses the hashcode of the underlying function, rather than of itself,
    # but I'm not sure.

# Automatically done after compiling bar
bar.__get__ = __method_wrapper(bar)

"Связывание", которое происходит в __get__, автоматически привязанное к bar (называемое дескриптором), кстати, является более или менее причиной того, почему вы должны явно указать параметры self для методов Python. В Javascript сам this является магическим; в Python, это всего лишь процесс привязки вещей к self, который является волшебным.;)

И да, вы можете явно установить метод __get__ на свои собственные объекты и сделать это для особых вещей, когда вы устанавливаете атрибут класса для экземпляра объекта, а затем получаете доступ к нему из экземпляр этого другого класса. Python чрезвычайно отражает.:) Но если вы хотите узнать, как это сделать, и получите действительно полное понимание ситуации, вы имеете много чтения .;)