Python execfile、inspect、globals() 和 metaclass 中的 __file__ 和 __name__ 问题

86 阅读2分钟

在 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")