Что такое исправление обезьян?

549

Я пытаюсь понять, что такое исправление обезьяны или исправление обезьяны?

Это что-то вроде перегрузки или делегирования методов / операторов?

Есть ли что-то общее с этими вещами?

Сергей Башаров
источник
Я думаю, что определение от Google является полезным и наиболее общим:Monkey patching is a technique to add, modify, or suppress the default behavior of a piece of code at runtime without changing its original source code.
Чарли Паркер

Ответы:

522

Нет, это не похоже ни на одну из этих вещей. Это просто динамическая замена атрибутов во время выполнения.

Например, рассмотрим класс, у которого есть метод get_data. Этот метод выполняет внешний поиск (например, в базе данных или веб-API), и различные другие методы в классе вызывают его. Однако в модульном тесте вы не хотите зависеть от внешнего источника данных - поэтому вы динамически заменяете get_dataметод заглушкой, которая возвращает некоторые фиксированные данные.

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

Но, как отметил комментатор , соблюдайте осторожность при установке обезьян:

  1. Если что-то еще, кроме вашей тестовой логики get_data, также вызовет, это также вызовет замену, исправленную обезьяной, а не оригинал - что может быть хорошим или плохим. Просто будь осторожен.

  2. Если существует некоторая переменная или атрибут, который также указывает на get_dataфункцию к моменту ее замены, этот псевдоним не изменит своего значения и будет продолжать указывать на оригинал get_data. (Почему? Python просто привязывает имя get_dataв вашем классе к другому объекту функции; на другие привязки имен это никак не влияет.)

Даниэль Роузман
источник
1
@LutzPrechelt, чтобы быть понятным для меня, что ты имеешь в виду pointing to the original get_data function? Вы имеете в виду, когда вы сохраняете функцию внутри переменной, если кто-то изменяет эту функцию, переменная будет продолжать указывать на старую?
fabriciorissetto
3
@fabriciorissetto: Обычно вы не изменяете функциональные объекты в Python. Когда вы исправляете обезьяну get_data, вы привязываете имя get_dataк фиктивной функции. Если какое-либо другое имя где-либо еще в программе связано с ранее известной функцией-функцией get_data, ничто не изменится для этого другого имени.
Лутц Пречелт
1
@LutzPrechelt Не могли бы вы объяснить немного больше об этом?
Кельвин Ку
Я думаю, что исправление обезьяны может быть полезно, особенно для отладки, и в функциях декораторов или объектных фабрик. Однако помните, что явное лучше, чем неявное, поэтому убедитесь, что ваш код нечувствителен к контексту, прочитайте «Goto считать вредным» и т. Д.
aoeu256
Итак, это просто что-то вроде использования функции 'eval', куда вы могли бы вставить новый код во время выполнения?
Wintermute
363

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

Простой пример выглядит так:

from SomeOtherProduct.SomeModule import SomeClass

def speak(self):
    return "ook ook eee eee eee!"

SomeClass.speak = speak

Источник: страница MonkeyPatch на вики Zope.

Paolo
источник
126

Что такое патч обезьяны?

Проще говоря, исправление обезьян вносит изменения в модуль или класс во время работы программы.

Пример использования

В документации Pandas есть пример исправления обезьян:

import pandas as pd
def just_foo_cols(self):
    """Get a list of column names containing the string 'foo'

    """
    return [x for x in self.columns if 'foo' in x]

pd.DataFrame.just_foo_cols = just_foo_cols # monkey-patch the DataFrame class
df = pd.DataFrame([list(range(4))], columns=["A","foo","foozball","bar"])
df.just_foo_cols()
del pd.DataFrame.just_foo_cols # you can also remove the new method

Чтобы разбить это, сначала мы импортируем наш модуль:

import pandas as pd

Затем мы создаем определение метода, которое существует несвязанным и свободным вне рамок любых определений классов (поскольку различие между функцией и несвязанным методом довольно бессмысленно, Python 3 устраняет несвязанный метод):

def just_foo_cols(self):
    """Get a list of column names containing the string 'foo'

    """
    return [x for x in self.columns if 'foo' in x]

Затем мы просто присоединяем этот метод к классу, на котором мы хотим его использовать:

pd.DataFrame.just_foo_cols = just_foo_cols # monkey-patch the DataFrame class

И тогда мы можем использовать метод в экземпляре класса и удалить метод, когда закончим:

df = pd.DataFrame([list(range(4))], columns=["A","foo","foozball","bar"])
df.just_foo_cols()
del pd.DataFrame.just_foo_cols # you can also remove the new method

Будьте осторожны

Если вы используете искажение имени (добавление к атрибуту префикса с двойным подчеркиванием, которое меняет имя, и которое я не рекомендую), вам придется вручную изменять имя, если вы делаете это. Так как я не рекомендую искажение имени, я не буду демонстрировать это здесь.

Пример тестирования

Как мы можем использовать эти знания, например, при тестировании?

Скажем, нам нужно смоделировать вызов извлечения данных из внешнего источника данных, который приводит к ошибке, потому что мы хотим обеспечить правильное поведение в таком случае. Мы можем обезопасить структуру данных, чтобы обеспечить такое поведение. (Таким образом, используя аналогичное имя метода, предложенное Дэниелом Роузманом :)

import datasource

def get_data(self):
    '''monkey patch datasource.Structure with this to simulate error'''
    raise datasource.DataRetrievalError

datasource.Structure.get_data = get_data

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

Простое выполнение вышеизложенного изменит Structureобъект на весь жизненный цикл процесса, поэтому вы захотите использовать настройки и разрывы в своих тестах юнитов, чтобы избежать этого, например:

def setUp(self):
    # retain a pointer to the actual real method:
    self.real_get_data = datasource.Structure.get_data
    # monkey patch it:
    datasource.Structure.get_data = get_data

def tearDown(self):
    # give the real method back to the Structure object:
    datasource.Structure.get_data = self.real_get_data

(Хотя в приведенном выше порядке, вероятно , было бы лучше идеи использовать mockбиблиотеку для исправления кода. mock«S patchдекоратора будет меньше ошибок , чем делать выше, что потребует больше строк коды и , следовательно , больше возможностей для внедрения ошибок Я еще не рассмотрел код, mockно я думаю, что он использует патч обезьян аналогичным образом.)

Аарон Холл
источник
так на бремя обезьяньего патча лежит бремя ссылки на реальный метод? например, что произойдет, если кто-то забудет шаг «сохранить указатель», он потерян?
Томми
3
@Tommy Если ссылки на «перезаписанный» метод приводят к нулю - это сборщик мусора, и, таким образом, «теряется» на весь срок службы процесса (или если модуль, в котором он был создан, перезагружается, но это обычно не рекомендуется).
Аарон Холл
33

Согласно Википедии :

В Python термин «исправление обезьяны» относится только к динамическим модификациям класса или модуля во время выполнения, мотивированным намерением исправлять существующий сторонний код в качестве обходного пути для ошибки или функции, которая не действует по вашему желанию.

Дэвид Хеффернан
источник
16

Во-первых: исправление обезьян - злой хак (на мой взгляд).

Он часто используется для замены метода на уровне модуля или класса пользовательской реализацией.

Самым распространенным вариантом использования является добавление обходного пути для ошибки в модуле или классе, когда вы не можете заменить исходный код. В этом случае вы заменяете «неправильный» код с помощью «обезьяньих патчей» реализацией внутри вашего собственного модуля / пакета.

Андреас Юнг
источник
8
В случае, если на некоторых модулях мартышка-патчинг одинакова: вы обречены.
Андреас Юнг
49
Хотя его мощь действительно может быть опасной в целом, она отлично подходит для тестирования
dkrikun
1
Наиболее распространенный вариант использования на самом деле для тестирования, особенно модульных тестов. Вы хотите протестировать только свой код, поэтому вы исправляете любой внешний вызов, чтобы получить ожидаемый результат.
брокколи
1
это не зло, я использую его для исправления ошибок в программном обеспечении других людей, пока не выйдет новый релиз, вместо того, чтобы разветвляться и создавать новую зависимость.
Нуреттин
1
Исправление обезьяны может быть сделано «чисто функциональным» способом, а не изменчивым, «контекстно-зависимым», goto-подобным способом, только делая исправления внутри декораторов, которые возвращают новую исправленную версию вашего класса / метода (а не изменяют ее). Многие программисты на C # / Java не знают о разработке на основе REPL, поэтому они пишут в своих IDE, что требует статического определения всего. Поскольку в C # / Java не было патчей для обезьян, они предполагают, что это зло, когда они видят его в JavaScript, Smalltalk, Lisp, Python и т. Д., Что противоречит их практике разработки на основе статических IDE.
aoeu256
13

Патч обезьяны может быть сделан только на динамических языках, примером чего является Python. Изменение метода во время выполнения вместо обновления определения объекта является одним из примеров, аналогичным образом, добавление атрибутов (методов или переменных) во время выполнения считается исправлением обезьяны. Это часто делается при работе с модулями, для которых у вас нет источника, так что определения объектов не могут быть легко изменены.

Это считается плохим, потому что это означает, что определение объекта не полностью или точно не описывает, как он на самом деле ведет себя.

Аарон Дюфур
источник
Однако исправление обезьяны может быть полезно, если вместо изменения существующего объекта или класса вы создадите новую версию объекта с пропатченными членами в декораторе, который кричит «эй, я собираюсь пропатчить вас».
aoeu256
Вы можете использовать аннотации для пропатченных элементов, чтобы сохранить в пропатченном элементе, какой декоратор использовался для исправления в патчах. Допустим, у вас есть отменяемый декоратор, который создает новую отменяемую версию функционального объекта с помощью метода отмены. Вы можете поместить в декоратор поле патчера, указывающее на ваш отменяемый декоратор.
aoeu256
5

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

Поскольку Python является динамическим языком программирования, классы изменчивы, поэтому вы можете открыть их и изменить или даже заменить.

Камаль
источник
1

Что такое исправление обезьян? Исправление обезьян - это метод, используемый для динамического обновления поведения фрагмента кода во время выполнения.

Зачем использовать исправления обезьян? Это позволяет нам изменять или расширять поведение библиотек, модулей, классов или методов во время выполнения без фактического изменения исходного кода.

Заключение Исправление обезьян - это классная техника, и теперь мы научились делать это на Python. Однако, как мы уже говорили, он имеет свои недостатки и должен использоваться осторожно.

Для получения дополнительной информации, пожалуйста, обратитесь [1]: https://medium.com/@nagillavenkatesh1234/monkey-patching-in-python-explained-with-examples-25eed0aea505

акаш кумар
источник