Python: как передать аргументы функции __code__ функции?

Следующие работы:

def spam():
    print "spam"
exec(spam.__code__)

спам

Но что, если spam принимает аргументы?

def spam(eggs):
    print "spam and", eggs
exec(spam.__code__)

TypeError: spam() принимает ровно 1 аргумент (задано 0)

Учитывая, что у меня нет доступа к самой функции, но только к объекту кода, как я могу передать аргументы объекту кода при его выполнении? Возможно ли это с помощью eval?

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

Я хочу сохранить небольшие функции Python в файл, чтобы их можно было вызвать, например. с другого компьютера. (Излишне говорить здесь, что этот usecase строго ограничивает возможные функции.) Расселение самого объекта функции не может работать, потому что это только сохраняет имя и модуль, где определена функция. Вместо этого я мог бы выбрать __code__ функции. Когда я снова распечатаю его, конечно, ссылка на функцию исчезла, поэтому я не могу назвать функцию. Я просто не имею его во время выполнения.

Еще одна утилита:

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

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

Ответ 1

Можете ли вы изменить функцию, чтобы не принимать какие-либо аргументы? Затем переменные просматриваются с локалей/глобалов, где вы можете поставить в exec:

>>> def spam():
...   print "spam and", eggs
... 
>>> exec(spam.__code__, {'eggs':'pasta'})
spam and pasta

(Почему бы просто не отправить целую функцию в виде строки? Pickle "def spam(eggs): print 'spam and', eggs" и exec строка (после проверки) с другой стороны.)

Ответ 2

Я полностью против использования __code __.

Хотя я любопытный человек, и это то, что кто-то теоретически мог бы сделать:

code # This is your code object that you want to execute

def new_func(eggs): pass
new_func.__code__ = code
new_func('eggs')

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

Ответ 3

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

Тем не менее, одно хакерское решение будет:

>>> def spam(eggs):
...     print "spam and %s" % eggs
...     
... 
>>> spam('bacon')
spam and bacon
>>> def util():
...     pass
...     
... 
>>> util.__code__ = spam.__code__
>>> util('bacon')
spam and bacon
>>> 

Ответ 4

Объект кода является частью функции, поэтому несколько ответов выше предлагают создать фиктивную функцию и заменить ее __code__ на codeObject. Здесь другой способ, который позволяет избежать и отбрасывать новый __code__:

import new
newFunction = new.function(codeObject, globals())

(Протестировано в Python 2.7, где spam.__code__ имеет имя spam.func_code.)

Ответ 5

Я не думаю, что вы можете передать аргументы либо exec, либо eval, чтобы они были переданы в объект кода.

Вы можете обратиться к строковой версии exec/eval, например. exec("spam(3)").

Вы можете создать еще один объект кода, который связывает этот аргумент, и затем выполнить это:

def spam_with_eggs():
   return spam(3)
exec(spam_with_eggs.__code__)

(Я думал, что вы также можете достичь этого с помощью functools.partial, но не получили его для работы).

EDIT:

Прочитав ваши дополнительные объяснения, я подумал о способах восстановить правильную функцию из объекта кода. Этот простой подход работал у меня (в python2.5):

def bar():pass
bar.func_code = spam.func_code
bar(3)  # yields "spam and 3"

Ответ 6

Мой метод, я делаю это более красивым

def f(x):
    print(x, x+1)

g = type(f)(f.__code__, globals(), "optional_name")

g(3)