Python元类,execfile,inspect,globals(),namespace

时间:2013-07-04 12:20:40

标签: python metaclass execfile

直接运行以下脚本时,它按预期运行:

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))
        #setattr(cls, "source", test_str)
        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__)
        #self.source = test_str
    def getsource(self):
        return self.source

class ChildB(ParentModel):
    name = "childb"
    pass

class ChildA(ChildB):
    name = "childa"
    pass

class ChildC(ChildA):
    name = "childc"
    pass

尝试通过python shell或其他脚本中的exec或execfile运行此脚本时出现困难。例如:

>>> 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

这会导致错误,如果execfile的全局命名空间参数接受了字典,则会出现混乱。但是:

>>> execfile("classes.py", globals())

同样,运行没有问题,但是:

>>> ns = dict(globals())
>>> 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

从回溯中,它与inspect相关,但是它应该在execfile(“classes.py”)或execfile(“classes.py”,globals())上出错。

那么,就这个错误而言,dict(globals())如何!= globals()以及为什么会导致这个错误?

编辑:读者应该参考Martijn Pieters和Lennart Regebro的答案以获得完整的图片。

2 个答案:

答案 0 :(得分:2)

当您使用execfile()执行python文件时,您正在当前命名空间中执行它。 REPL命名空间是一个内置模块:

>>> import sys
>>> sys.modules['__main__']
<module '__main__' (built-in)>

表示inspect.getsource()没有源文件要检索:

>>> sys.modules['__main__'].__file__
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'module' object has no attribute '__file__'
>>> import inspect
>>> inspect.getfile(sys.modules['__main__'])
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/usr/local/Cellar/python/2.7.5/Frameworks/Python.framework/Versions/2.7/lib/python2.7/inspect.py", line 403, in getfile
    raise TypeError('{!r} is a built-in module'.format(object))
TypeError: <module '__main__' (built-in)> is a built-in module

您的下一个问题是因为您使用execfile代码的模块设置始终是错误的。 inspect.getsource()无法确定您定义代码的位置,因为execfile()会绕过正常的导入机制:

$ cat test.py
execfile('classes.py')
$ python test.py
Traceback (most recent call last):
  File "test.py", line 1, in <module>
    execfile('classes.py')
  File "classes.py", line 13, in <module>
    class ParentModel(object):
  File "classes.py", line 9, in __init__
    setattr(cls, "source", inspect.getsource(cls))
  File "/usr/local/Cellar/python/2.7.5/Frameworks/Python.framework/Versions/2.7/lib/python2.7/inspect.py", line 701, in getsource
    lines, lnum = getsourcelines(object)
  File "/usr/local/Cellar/python/2.7.5/Frameworks/Python.framework/Versions/2.7/lib/python2.7/inspect.py", line 690, in getsourcelines
    lines, lnum = findsource(object)
  File "/usr/local/Cellar/python/2.7.5/Frameworks/Python.framework/Versions/2.7/lib/python2.7/inspect.py", line 564, in findsource
    raise IOError('could not find class definition')
IOError: could not find class definition

除非您直接从具有相同源代码的文件中运行,否则您的代码不会与execfile('classes.py', globals())一起使用。在某些平台上,它似乎可行,因为execfile最终会触发设置当前模块的__file__属性的代码。

实际完成这项工作的唯一方法是

  • sys.modules中创建一个假模块对象,并为其指定__file__属性,指向classes.py
  • 将名称空间传递给execfile(),将__name__设置为与假模块对象匹配。

演示:

>>> import sys
>>> import types
>>> sys.modules['fake_classes'] = types.ModuleType('fake_classes')
>>> sys.modules['fake_classes'].__file__='classes.py'
>>> ns = {'__name__': 'fake_classes'}
>>> execfile('classes.py', ns)
>>> >>> ns.keys()
['__module__', 'ChildA', '__builtins__', 'inspect', '__package__', 'met', 'ChildB', 'ChildC', 'ParentModel', '__name__', 'test_str']

为了使其明确,创建globals()的副本只会阻止execfile()修改当前模块名称空间(或您的REPL名称空间)。传递给execfile()的词典之间没有区别。

答案 1 :(得分:1)

这个问题不是dict(globals()) != globals(),因为它是。{1}}。这里的问题是,您执行globals()的上下文中的execfile()与您globals()中的classes.py不同。

当您传入命名空间时,您将替换原本将创建的命名空间。如果您传入名称空间,则会为classes模块创建一个名称空间。这意味着__main__将是classes.py文件。但是,当您传入调用execfile()的文件的名称空间时,将使用该名称空间,而__main__将是该模块。

这会导致检查无法在源代码中找到类定义,因为它查找的文件不正确。

如果传入一个空名称空间,它将根本找不到__main__,并且该类将被假定为内置函数,没有可用的源代码,并且将引发该效果的错误。

总之,你在这里犯的错误是想到globals()并且对解释者来说是全球性的,当它实际上是模块的全局时。

通过与Marjtin的讨论,可以清楚地知道OS X上的工作方式略有不同。这意味着即使您没有传入命名空间,也无法依赖此工作。

然后,这会导致你为什么这样做,以及你实际上想要实现的目标。