【问题标题】:Writing code to a file and re-importing as a module using __import__() and reload()使用 __import__() 和 reload() 将代码写入文件并作为模块重新导入
【发布时间】:2013-02-24 16:16:41
【问题描述】:

我一直在尝试为我的程序实现 __import__() 和 reload() ,但没有成功。我有一个字符串,我将其写入 .py 文件(模块),然后将其作为模块加载。然后我对该 .py 文件(模块)进行更改并写入它,但我似乎无法在新更新的模块中更改返回值。这是我的代码:

str1 = '''
def run():
    return 10

'''
f = open('mycode.py','w')
f.write(str1)
f.close()
mymodule = __import__('mycode')

es = 'number = mymodule.run()'
exec(es)
print "number", number


str2 = '''
def run():
    return 99

'''
f = open('mycode.py','w')
f.write(str2)
f.close()

mymodule = reload(mymodule)
mymodule = __import__('mycode')

es = 'number = mymodule.run()'
exec(es)
print "number", number


OUTPUT:
>> number 10
>> number 10 # should be 99

我看过这里:Re-import the same python module with __import__() in a fail-safe way

这里:

reloading module which has been imported to another module

这里:

How do I unload (reload) a Python module?

但我无法提出解决方案。任何帮助,将不胜感激。谢谢。

保罗

编辑

我之所以使用 exec(es) 是因为如果我有多个参数,我想自定义 es。这是一个例子:

p = [2,1]
p2 = [3,4,5]
p3 = [100,200,300,500]

str1 = '''
def run(x,y):
        return x + y

'''

with open('mycode.py','w') as f:
    f.write(str)
import mycode as mymodule

# how do i do this dynamically, 
# how to handle it when # of paremeters for run() change?
print mymodule.run(p[0],p[1])  # hard-coded parameter passing
# or, print mymodule.run(p[0],p3[1],p[2]) # if run() is changed

所以问题是我的 run() 可以有不同的参数。它可以是 run(x,y) 或 run(larry, moe, curly, hickory, dickory, dock)。如何动态地将多个参数传递给 run()?谢谢。

【问题讨论】:

  • 我知道我没有回答这个问题,但如果你想在运行时创建新行为,这不是要走的路。在 Python 中,您可以在运行时重新定义和修补类,而无需 reload()reload 机制充其量是脆弱的——它适用于 REPL,而不是用于编程用途。
  • 如果你真的想更换一个模块,你可以随时访问sys.modules。这就是python-sh 所做的,例如
  • 同时,你为什么在这里使用exec(es) 而不仅仅是number = mymodule.run()?你期待它做一些不同的事情吗? (如果是,那是什么?)或者只是增加更多的复杂性以使问题变得更加困难?
  • 我正在使用 exec() 因为我希望能够更改 run() 的参数,例如运行(10,30)。我的解释中没有包含这一点。
  • 调用具有动态数量参数的函数的处理方式很简单:执行mymodule.run(*p)。如果有两个元素,那就是mymodule.run(p[0], p[1]),如果没有元素,那就是mymodule.run()。不需要exec

标签: python


【解决方案1】:

您的实际问题非常简单。我将解释如何解决它。但实际上,你不应该这样做。不管你想做什么,你都做错了。

这并不是说没有理由任何人都可能想做这样的事情。但我非常有信心,任何使用exec 的人,因为他不知道你可以将函数作为一等值传递,使用文字模块名称调用__import__ 等等,都没有这样的理由。如果你解释了你实际上想要做什么,我们可以解释它。

但与此同时:

当您执行第一个import 时,它会创建一个mycode.pyc 文件,而reload 只是重新加载该文件。

您可以通过删除mycode.pyc 来验证这一点,然后运行python -B reloadtest.py 而不是python reloadtest.py。这显然为您提供了一种可能的解决方法。

或者,您可以在调用reload 之前明确地os.unlink('mycode.pyc')。我认为大多数人会认为这非常不符合 Python 标准,但我认为这与您最初尝试做的事情一样。

最后,如果您创建一个新的mycode.py 文件而不是就地重写现有文件,我认为这会解决问题。


同时,您的代码与以下代码完全相同。 (为了解决您对 cme​​ts 的困惑:完全相同意味着它将有完全相同的问题,并且完全相同的解决方案将解决它。)它也更简单、更 Python 并且更易于调试,因为它只涉及 一个巨大的反模式(在交互式解释器之外的任何地方使用重新加载)而不是四个:

str1 = '''
def run():
    return 10

'''
with open('mycode.py', 'w') as f:
    f.write(str1)
import mycode as mymodule

print "number", mymodule.run()

str2 = '''
def run():
    return 99

'''
with open('mycode.py', 'w') as f:
    f.write(str2)

reload(mymodule)

print "number", mymodule.run()

在您的编辑中,您解释说您正在使用exec,因为您想传递动态可变数量的参数:

它可以是 run(x,y) 或 run(larry, moe, curly, hickory, dickory, dock)。如何动态地将多个参数传递给run()?

简单。只需将x, ylarry, moe, curly, hickory, dickory, dock 放入tuplelist 或其他序列中,然后使用run(*seq)

例如,我假设你有这样的代码:

es = 'number = mymodule.run({})'.format(','.join(params))
exec(es)

您可以将其替换为:

number = mymodule.run(*params)

事实上,几乎任何情况下你都可以想象你认为你需要exec,有更好的方法来做到这一点。如果您无法弄清楚,您可以在 Google 或 StackOverflow 上进行快速搜索,如果失败了,只需询问如何操作即可。使用exec 进行动态代码生成对于 tcl 来说是惯用的,并且在几年前曾经用于 PHP 和 JavaScript,但在 Python 中几乎从来都不是正确的做法。

而且,您现在可能已经猜到了,动态创建模块到import/reload/execfile/etc 也是如此。

例如,您的真实代码可能正在尝试执行以下操作:

def make_new_module(n):
    str = '''
    def run():
        return {}        
    '''.format(n)
    f = open('mycode.py','w')
    f.write(str1)
    f.close()
    return __import__('mycode')

但您可以动态定义函数:

def make_new_function(n):
    def run():
        return n
    return run

如果您确实需要将其命名为mymodule.run,您可以轻松地创建一个命名空间,如类、类实例、namedtuple 等来粘贴它。

如果你真的需要,你甚至可以即时创建一个新模块(尽管我敢打赌你不需要):

import types
def make_new_module(n):
    def run():
        return n
    mymodule = types.ModuleType('mymodule')
    mymodule.run = run
    return mymodule

在现有模块中重新绑定函数更加容易:

def change_mymodule_run(n):
    def run():
        return n
    mymodule.run = run

是的,模块是一等值,就像函数一样,所以如果module 是一个动态参数而不是静态值,你可以很容易地做到这一点:

def change_module_run(module, n):
    def run():
        return n
    module.run = run

【讨论】:

  • 感谢对更多 Pythonic 代码的建议和更正。我更新了我原来的问题来解释我为什么使用 exec()。
  • 由于某种原因,上面的代码仍然只给我输出:>>数字 10 而不是 99。我正在运行 Python 2.7.1。 :(
  • @Paul:正如我所解释的,上面的代码与您的原始代码相同。所以当然它有完全相同的问题。它可以用完全相同的方式解决:python -Bos.unlink('mycode.pyc') 等。或者,最好不要一开始就尝试动态创建模块。
  • 我尝试动态创建模块的原因是因为我将 python 代码嵌入到我正在制作的税务会计测验程序的 xml 文件中。我需要将逻辑和变量嵌入到每个独特的问题中。
  • @Paul:即使是这样的情况,通常最好嵌入 Python 源文本以外的内容。首先,你总是可以pickle 一个函数而不是转储它的文本。但通常会有更好的东西。例如,如果函数仅基于某些数据值而有所不同,则嵌入这些值。如果它们需要更复杂一点,请编写一种简单的特定于领域的语言。即使在您确实需要动态执行任意 Python 源代码的极少数情况下,exec 通常也比import 更合适。
【解决方案2】:

正如一些评论者所说,您可能不需要编写模块来获得动态代码。下面是一个替代实现,它创建了两个不同版本的 run 函数,它们给出了不同的结果:

def run_function_factory(n):
    def run_function():
        return n
    return run_function

run1 = run_function_factory(10)
print("run1 returns", run1()) # prints "run1 returns 10"

run2 = run_function_factory(99)
print("run2 returns", run2()) # prints "run2 returns 99"

显然,您的实际代码会更复杂,但对于您需要的几乎任何变化,都应该可以使用类似的编码风格(将函数视为一流对象)。例如,如果run 接受两个参数,ab,并在返回之前对它们和n 进行一些算术运算,你会使用这样的东西:

def run_function_factory(n):
    def run_function(a, b):
        return a*n + b

run1 = run_function_factory(10)
for x in range(3):
    for y in range(2,5):
        print(run1(x,y)) # prints 2,3,4,12,13,14,22,23,24 (one per line)

【讨论】:

  • 谢谢,下次我需要它时会有所帮助。 :D
猜你喜欢
  • 2014-03-22
  • 1970-01-01
  • 2019-01-08
  • 1970-01-01
  • 1970-01-01
  • 2023-03-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多