2015-05-05 2 views
5

Мне нужно изменить вызов функции внутри другой функции во время выполнения.Изменить ссылку на функцию во время выполнения в Python

Рассмотрим следующий код:

def now(): 
    print "Hello World!" 

class Sim: 
    def __init__(self, arg, msg): 
     self.msg = msg 
     self.func = arg 
     self.patch(self.func) 

    def now(self): 
     print self.msg 

    def run(self): 
     self.func() 

    def patch(self, func): 
     # Any references to the global now() in func 
     # are replaced with the self.now() method. 

def myfunc(): 
    now() 

Тогда ...

>>> a = Sim(myfunc, "Hello Locals #1") 
>>> b = Sim(myfunc, "Hello Locals #2") 
>>> b.run() 
Hello Locals #2 
>>> a.run() 
Hello Locals #1 

Один пользователь написал код, myfunc(), что делает вызов глобально определенной функции now(), и я не могу изменить его , Но я хочу, чтобы он вместо этого вызывал метод экземпляра Sim. Поэтому мне нужно будет «запланировать» функцию myfunc() во время выполнения.

Как я могу это сделать?

Одним из возможных решений является отредактировать байт-код, как это делается здесь: http://www.jonathon-vogel.com/posts/patching_function_bytecode_with_python/, но мне интересно, есть ли более простой способ.

+0

В вашем фактическом прецеденте используются 'now' и' myfunc', определенные в том же модуле, что и другие? Вам небезразлично, повлияли ли другие применения «сейчас»? Если они находятся в разных модулях, как 'myfunc' получает доступ к функции' now'? Необходимый подход может быть немного иным, если он «из импорта somemodule now ... now()' vs 'import somemodule ... somemodule.now()'. – Weeble

+0

Да, это имеет значение. Я обновил этот пример, чтобы объяснить это. Конечный пользователь будет импортировать глобальные '' now'' и '' Sim'' из библиотеки в свой код. Я мог бы изменить глобальный '' now'', но я не могу позволить себе дорогостоящий поиск внутри глобального '' now'', чтобы найти локальную версию. – jpcgt

ответ

2

Это сложнее, чем может показаться. Для этого вам нужно:

  • Создать dict подкласс с помощью метода __missing__ провести свое новое значение now. Метод __missing__ затем захватывает любые предметы, не содержащиеся в словаре, от обычного globals() dict.

  • Создайте новый функциональный объект из существующей функции myfunc(), сохранив свой объект кода, но используя новый словарь, созданный для глобальных переменных.

  • Назначьте новую функцию обратно в глобальные переменные, используя ее имя функции.

Вот как:

def now(): 
    print "Hello World!" 

class NowGlobals(dict): 
    def __init__(self, now, globals): 
     self["now"] = now 
     self.globals = globals 
    def __missing__(self, key): 
     return self.globals[key] 

class Sim(object): 
    def __init__(self, func): 
     func = self.patch(func) 
     self.func = func 
    def now(self): 
     print "Hello locals!" 
    def patch(self, func): 
     funcname = func.__name__ 
     nowglobals = NowGlobals(self.now, func.func_globals) 
     func = type(func)(func.func_code, nowglobals) 
     globals()[funcname] = func 
     return func 

def myfunc(): 
    now() 

sim = Sim(myfunc) 
myfunc() 

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

Если myfunc находится в другом модуле, вам необходимо переписать Sim.patch() для исправления этого пространства имен, например. module.myfunc = sim.patch(module.myfunc)

+0

Будет ли эта работа работать с двумя разными экземплярами '' Sim() '' как в обновленном коде? Похоже, что он модифицирует оригинальный '' myfunc'' и будет изменять предыдущее исправление. Возможно, глубокое копирование '' myfunc'', а затем исправление может сделать трюк? – jpcgt

+0

Должен работать с несколькими экземплярами. Очевидно, что только одна версия 'myfunc' может быть в глобальном масштабе одновременно, но если вы вызовете ее через экземпляр' Sim' (либо сделав 'Sim' вызываемым, добавив метод' __call__', либо просто вызвав 'sim. func() '), каждый использует свою собственную версию' myfunc'. Вы также можете отложить исправление до вызова экземпляра. – kindall

+0

Это отлично работало. Я просто хотел бы указать, что '' globals() [funcname] = name'' изменит исходный '' myfunc'', поэтому он должен быть необязательным в зависимости от требуемого результата. Я лично просто прокомментировал это и теперь делаю то, что хотел. – jpcgt

-1

Функции имеют атрибут func_globals. В этом случае вы реализуете patch так:

def now(): 
    print "Hello World!" 


class Sim: 
    def __init__(self, arg): 
     self.func = arg 
     self.patch(self.func) 
     self.func() 

    def now(self): 
     print "Hello Locals!" 

    def patch(self, func): 
     # Any references to the global now() in func 
     # are replaced with the self.now() method. 
     func.func_globals['now'] = self.now 


def myfunc(): 
    now() 


Sim(myfunc) 

который печатает:

Hello World! 
Hello Locals! 
+0

Это вызовет * all * вызовы 'now' для замены, а не просто вызовы' now' из 'myfunc'. – BrenBarn

+0

Упс. Пропустил это. Редактирование моего ответа. –

+0

Это смешивает функцию, которую вы хотите исправить, и функцию, которую вы хотите использовать другую функцию. Кроме того, он по-прежнему заменяет вызовы 'now' от других функций модуля. – user2357112

0

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

def now(): 
    print "Hello World!" 

class Sim: 
    def __init__(self, arg, msg): 
     self.msg = msg 
    self.func = arg 
    self.patch(self.func) 

    def now(self): 
    print self.msg 

    def run(self): 
    self.func() 

    def patch(self, func): 
    # Any references to the global now() in func 
    # are replaced with the self.now() method. 

    def newfunc(): 
     global now 
     tmp = now 
     now = self.now 
     func() 
     now = tmp 

    self.func = newfunc 


def myfunc(): 
    now() 
1

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

Это может быть сделано путем замены myfunc с функцией обертки, которая делает копию глобального переменного словаря, заменяет значение обижая 'now', делает новую функцию с кодом объектом исходного myfunc и новым глобальным словарем, и затем вызывает эту новую функцию вместо оригинала myfunc. Как это:

import types 
def now(): 
    print("Hello World!") 

class Sim: 
    def __init__(self, arg): 
     self.func = arg 
     self.patch(self.func) 
     self.func() 

    def now(self): 
     print("Hello Locals!") 

    def patch(self, func): 

     def wrapper(*args, **kw): 
      globalcopy = globals().copy() 
      globalcopy['now'] = self.now 
      newfunc = types.FunctionType(func.__code__, globalcopy, func.__name__) 
      return newfunc(*args, **kw) 
     globals()[func.__name__] = wrapper 

def myfunc(): 
    now() 

def otherfunc(): 
    now() 

Тогда:

>>> myfunc() 
Hello World! 
>>> otherfunc() 
Hello World! 
>>> Sim(myfunc) 
Hello World! 
<__main__.Sim instance at 0x0000000002AD96C8> 
>>> myfunc() 
Hello Locals! 
>>> otherfunc() 
Hello World! 

Есть много причин, чтобы не делать этого. Он не будет работать, если функция now, к которой обращаются myfunc, находится не в том же модуле, что и Sim. Это может сломать другие вещи. Кроме того, создание простого экземпляра класса, такого как Sim(myfunc), изменяет глобальное состояние таким образом, действительно является злом.

0

Вы можете использовать пакет mock, который уже выполняет большую часть напряженной работы по исправлению вызова функции. Это сторонняя установка в Python 2 (и ранних версиях 3?), Но часть стандартной библиотеки Python 3 - unittest.mock. В основном это предназначено для тестирования, но вы можете попробовать использовать его здесь.

import mock 

def now(): 
    print "Hello World!" 

class Sim: 
    # Your code from above here 

def myfunc(): 
    now() 

myfunc() # Outputs Hello World! 

s = Sim(myfunc, "Hello Locals #1") 
mock.patch('__main__.now', wraps=s.now).start() 

myfunc() # Now outputs Hello Locals #1 
Смежные вопросы