在 Python 中,execfile() 函数可以在指定的全局命名空间中执行一个 Python 脚本,常用于动态加载和执行代码。当我们尝试使用 execfile() 执行一个包含 metaclass 的脚本时,可能会遇到错误。
脚本 classes.py 内容如下:
import inspect
__module__ = "__main__"
__file__ = "classes.py"
test_str = "test"
class met(type):
def __init__(cls, name, bases, dct):
setattr(cls, "source", inspect.getsource(cls))
super(met, cls).__init__(name, bases, dct)
class ParentModel(object):
__metaclass__ = met
def __init__(self):
super(object, self).__init__(ParentModel.__class__)
def setsource(self):
self.source = inspect.getsource(self.__class__)
def getsource(self):
return self.source
class ChildB(ParentModel):
name = "childb"
pass
class ChildA(ChildB):
name = "childa"
pass
class ChildC(ChildA):
name = "childc"
pass
当我们直接运行 classes.py 脚本时,可以正常运行:
$ python classes.py
1、问题场景
但当我们尝试使用 execfile() 执行 classes.py 脚本时,会遇到错误:
>>> ns = {}
>>> execfile("classes.py", ns)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "classes.py", line 13, in <module>
class ParentModel(object):
File "classes.py", line 9, in __init__
setattr(cls, "source", inspect.getsource(cls))
File "D:\Python27\lib\inspect.py", line 701, in getsource
lines, lnum = getsourcelines(object)
File "D:\Python27\lib\inspect.py", line 690, in getsourcelines
lines, lnum = findsource(object)
File "D:\Python27\lib\inspect.py", line 526, in findsource
file = getfile(object)
File "D:\Python27\lib\inspect.py", line 408, in getfile
raise TypeError('{!r} is a built-in class'.format(object))
TypeError: <module '__builtin__' (built-in)> is a built-in class
2、问题原因
这个问题的原因是,当我们使用 execfile() 执行 classes.py 脚本时,脚本中的 main 模块变成了 classes.py 模块,而之前在 classes.py 脚本中使用的 inspect.getsource() 函数会试图从 main 模块中获取源代码,但 main 模块此时是 classes.py 模块,导致 inspect.getsource() 无法找到源代码,并引发错误。
2、解决方案
为了解决这个问题,我们需要手动创建一个假的 main 模块,并将其添加到 sys.modules 中,以便 inspect.getsource() 可以正确地找到源代码。
import sys
import types
# 创建一个假的 __main__ 模块
fake_main = types.ModuleType('__main__')
fake_main.__file__ = 'classes.py'
# 将假的 __main__ 模块添加到 sys.modules 中
sys.modules['__main__'] = fake_main
# 使用 execfile() 执行 classes.py 脚本
execfile("classes.py")
这样,当我们使用 execfile() 执行 classes.py 脚本时,就不会再遇到错误了。
代码例子
import inspect
__module__ = "__main__"
__file__ = "classes.py"
test_str = "test"
class met(type):
def __init__(cls, name, bases, dct):
setattr(cls, "source", inspect.getsource(cls))
super(met, cls).__init__(name, bases, dct)
class ParentModel(object):
__metaclass__ = met
def __init__(self):
super(object, self).__init__(ParentModel.__class__)
def setsource(self):
self.source = inspect.getsource(self.__class__)
def getsource(self):
return self.source
class ChildB(ParentModel):
name = "childb"
pass
class ChildA(ChildB):
name = "childa"
pass
class ChildC(ChildA):
name = "childc"
pass
# 创建一个假的 __main__ 模块
fake_main = types.ModuleType('__main__')
fake_main.__file__ = 'classes.py'
# 将假的 __main__ 模块添加到 sys.modules 中
sys.modules['__main__'] = fake_main
# 使用 execfile() 执行 classes.py 脚本
execfile("classes.py")