在Python中使用execfile时出现NameError

5 投票
4 回答
9223 浏览
提问于 2025-04-15 11:54

我的应用程序有一个按钮,可以动态执行一个Python脚本,使用的是execfile。如果我在脚本里定义了一个函数(比如spam()),然后想在另一个函数(比如eggs())里使用这个函数,就会出现一个错误:

NameError: global name 'spam' is not defined

那么,从eggs()里正确调用spam()函数的方法是什么呢?

#mainprogram.py
class mainprogram():
    def runme(self):
        execfile("myscript.py")

>>> this = mainprogram()
>>> this.runme()

# myscript.py
def spam():
    print "spam"

def eggs():
    spam()

eggs()

另外,我似乎也无法在脚本中执行我主应用程序里的一个方法。也就是说:

#mainprogram.py
class mainprogram():
    def on_cmdRunScript_mouseClick( self, event ):
        execfile("my2ndscript.py")
    def bleh():
        print "bleh"

 #my2ndscript.py
 bleh()

出现的错误是:

NameError: name 'bleh' is not defined

那么,从my2ndscript.py里正确调用bleh()的方法是什么呢?

编辑:更新了第一个问题

4 个回答

9

Addy689 解释了真正的问题:当从一个函数中调用 execfile() 时,会出现这个问题。而在全局空间中调用 execfile() 是没问题的。这就是为什么很多回答说“对我来说可以”的原因。

不过,修改被调用的脚本可能并不可行。所以我在这里分享我认为最好的解决方案,这个方案是在另一个与 exec() 函数相关的问题中找到的(在那篇帖子中:https://stackoverflow.com/a/11754346/1808778)。这个方法在 execfile() 中也同样适用。

def callingFunction(filename)
    # ... 
    d = dict(locals(), **globals())
    execfile(filename, d, d )

这个解决方案的好处是,我们不需要了解被调用的脚本:实际上执行的是在 if name == main 中指定的函数。

10

自从你发帖以来已经过去了3年8个月,我想你应该已经解决了第一个问题。不过,由于还没有人提供解决方案(主要是因为没有人遇到第一个问题),所以我来分享我的解决办法。

[更新]

我之前提供的解决方案是错误的。下面我会提供正确的解决方案,并详细解释我执行的代码。

问题出在Python的execfile()这个内置函数上。这也是为什么这个函数在Python 3.x中被弃用的原因之一。

当你在runme()函数里执行execfile()时,spam()eggs()这两个对象被加载到了runme()的命名空间里,而不是全局命名空间里(理想情况下应该是这样)。看看下面的代码:

myscript.py

def spam():
    print 'spam'

def eggs():
    if 'spam' not in globals():
        print 'method spam() is not present in global namespace'
    spam()

try:
    eggs()
except Exception as e:
    print e

mainprogram.py

class mainprogram():
    def runme(self):
        execfile("myscript.py")
        print 'Objects lying in local namespace of runme() are -'
        print locals()

this = mainprogram()
this.runme()

解释器输出

>>>import mainprogram
method spam() is not present in global namespace
name 'spam' is not defined
Objects lying in local namespace of runme() are -
{'e': NameError("name 'spam' is not defined",), 'spam': <function spam at 0x000000000000002B>, 'eggs': <function eggs at 0x000000000000002C>, 'self': <mainprogram.mainprogram instance at 0x000000000000002D>}

从输出中你可以看到,spam()并不在全局命名空间里,而是在runme()的命名空间里。所以理论上,正确调用spam()的方式应该是

def eggs():
    global this
    this.runme.spam()

但是,在runme()的命名空间里是无法访问到spam()的。因此,解决办法是将spam()放入全局命名空间,方法如下:

myscript.py

global spam
def spam():
    print "spam"

def eggs():
    spam()

eggs()

这样就能确保在globals()字典(也就是全局命名空间)里创建一个对spam()对象的引用,使得eggs()可以调用它。

2

在第二种情况下,你需要用到import(不确定“mainprogram.py”是否在你的$PYTHONPATH里)

#mainprogram.py
class mainprogram:
    def runme(self):
        execfile("my2ndscript.py")
    def bleh(self):
        print "bleh"
if __name__ == '__main__':
    mainprogram().runme()

#my2ndscript.py
import mainprogram
x = mainprogram.mainprogram()
x.bleh()

但这样会创建一个mainprogram的第二个实例。或者,更好的方法是:

#mainprogram.py
class mainprogram:
    def runme(self):
        execfile("my2ndscript.py", globals={'this': self})
    def bleh(self):
        print "bleh"
if __name__ == '__main__':
    mainprogram().runme()

#my2ndscript.py
this.bleh()

我觉得execfile并不是解决你问题的正确方法。为什么不试试用import或者__import__(如果脚本在这几次点击之间有变化,可以用reload())呢?

#mainprogram.py
import my2ndscript

class mainprogram:
    def runme(self):
        reload(my2ndscript)
        my2ndscript.main(self)
    def bleh(self):
        print "bleh"

if __name__ == '__main__':
    mainprogram().runme()

#my2ndscript.py
def main(program):
    program.bleh()

撰写回答