如何将参数传递给函数的 __code__?

32
以下内容可行:
def spam():
    print "spam"
exec(spam.__code__)

垃圾邮件

但是如果 spam 需要参数怎么办?

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

TypeError: spam()需要1个参数,但未提供任何参数

假设我只能访问代码对象而无法访问函数本身,那么在执行代码对象时如何传递参数?使用eval可以吗?

编辑:由于大多数读者倾向于不相信这种用法的有用性,请看以下用例:

我想将小型Python函数保存到文件中,以便可以从另一台计算机上调用它们。(不用说,这种用例严重限制了可能的函数。)对函数对象本身进行pickle处理是行不通的,因为这只会保存函数定义所在的名称和模块。相反,我可以pickle函数的__code__。当我再次unpickle时,显然对函数的引用消失了,这就是为什么我无法调用该函数的原因。我在运行时根本没有它。

另一个用例:

我在一个文件中编写了几个函数,用于计算一些数据并将其存储在硬盘上。这些计算需要很长时间,因此我不想每次都执行这些函数,而只想在函数实现更改时才执行它们。

我已经为整个模块而不是单个函数运行了一个版本。它通过查看实现模块的文件的修改时间来工作。但是,如果我有许多不想将其分开为单个文件的函数,则这不是一个选项。


4
只需调用函数,结束,就这样。 - user395760
7
在这个简单的示例中,我当然会这样做。我不会愚蠢到看不出来。但是如果我只有代码对象本身呢? - Turion
2
那么,我想说你有一个更严重的问题。你很少(真的很少)需要在这个抽象层面上摆弄东西,而默认情况下我怀疑你不处于这种情况,所以建议你确保获得一个函数而不是代码对象。 - user395760
一定有更简单的方法。不知道函数具体做什么就很难考虑细节,但只序列化重构函数所需的一小部分数据应该是完全可能的(例如,lambda x: 10 < x < 20 可以缩减为上下界)。如果这不是一个选项,请参见 https://dev59.com/0HM_5IYBdhLWcg3wvFvI。 - user395760
4
你希望将经过腌制的函数字节码通过网络发送,并且希望客户端能够从某个随意的位置允许代码执行?哇,这种做法在很多方面都非常危险...你需要的是一种远程过程调用(RPC)的形式。不要走捷径。 - Y.H Wong
显示剩余7条评论
6个回答

14

我完全反对这种使用方式__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__来在运行时加载代码。


我也喜欢这个解决方案,但不幸的是我只能选择其中一个解决方案。 但我同意,这确实很hacky。 - Turion
4
你也可以使用函数类型来构建一个函数。例如:new_func = type(lambda: 0)(code, globals())。这种方式稍微显得正式一些。你可以编写一个函数,从代码对象中构造出一个包装器函数,并调用生成的函数。 - kindall
4
这是一个改进。为了增加您的解决方案的可读性,我建议使用types.FunctionType替代type(lambda: 0) - Turion

11

您能否修改该函数,使其不需要任何参数?然后从局部/全局变量中查找变量,在执行exec时可以传入:

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

为什么不将整个函数作为字符串发送? 使用Pickle "def spam(eggs):print'spam and',eggs",并在另一端验证后exec字符串。


将此与 https://dev59.com/2XM_5IYBdhLWcg3wXx1N#33112180 结合起来,我认为你已经有了一个完全可行的答案,尽管我无法让链接的答案做任何事情。 - Hack5

7
我认为在您的大型应用程序中可能存在一些设计考虑,这使您不必担心此问题,例如拥有某些“已知良好和有效”的函数集合作为模块分发,执行代理知道这些模块的内容等等。
话虽如此,一个不太正式的解决方案是:
>>> 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
>>> 

我喜欢这个,就像Gustav Larsson的解决方案一样。 - Turion

5
一个代码对象是函数的一部分,因此前面有几个答案建议创建一个虚拟函数并替换其__code__为您的codeObject。这里提供另一种方法,避免创建并丢弃新的__code__
import new
newFunction = new.function(codeObject, globals())

(在 Python 2.7 中测试,其中 spam.__code__ 被命名为 spam.func_code。)

1
这应该被标记为最正确的答案,尽管另一个答案中的评论仍然使用那个奇怪的 hack,通过 types.FunctionType 提到了相同的内容(对于新读者来说,这个地方和想法都很难找)。
然而,对其他答案的引用应该被删除/不在顶部;并且示例应该足够完整,例如 new.function(spam_code_object, globals())("some eggs")
(Python 2.7 和 2.6 也支持 func.__code__ 除了 func.func_code
- kxr

4

我的方法,我认为它更加优美

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

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

g(3)

这与其他解决方案没有显著的区别。 - Turion
这很有趣,更准确地说,你也可以提供默认参数以防万一。将其作为关键字参数添加到构造函数中 argdefs = f.__defaults__ - aliqandil

2

我认为你不能将参数传递给execeval,以便它们被传递到代码对象。

你可以使用exec/eval的字符串版本,例如:exec("spam(3)")

你可以创建另一个绑定参数的代码对象,然后执行它:

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

我认为您也可以使用functools.partial来实现这一点,但我没有成功。

编辑:

在阅读了您的额外解释后,我想到了从代码对象重新建立正确函数的方法。这种简单的方法对我有效(在python2.5中):

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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接