0

When the following script is run directly, it operates as anticipated:

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

The difficulty arises when attempting to run this script through exec or execfile in the python shell or another script. For instance:

>>> execfile("classes.py")

Runs without issue, however:

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

This results in an error, which is confusing given dictionaries are accepted for the global namespace argument of execfile. But:

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

Again, runs without issue, though:

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

From the traceback, its related to inspect, however it should error on execfile("classes.py") or execfile("classes.py", globals()) as well.

So, in terms of this error, how is dict(globals()) != globals() and why does this cause this error ?

EDIT: Reader should refer to both Martijn Pieters and Lennart Regebro answers for a complete picture.

dilbert
  • 3,008
  • 1
  • 25
  • 34

2 Answers2

3

When you execute a python file with execfile() you are executing it in the current namespace. The REPL namespace is a built-in module:

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

which means there is no sourcefile for inspect.getsource() to retrieve:

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

Your next problem is that because you use execfile the module set for your code is always going to be wrong. inspect.getsource() cannot determine where you defined the code, as execfile() bypasses the normal import mechanisms:

$ 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

At no point does your code work with execfile('classes.py', globals()) unless you run it directly from a file that has the same source code. On some platforms it may appear to work because execfile ends up triggering code that sets the __file__ attribute of the current module.

The only way you actually make this work is to

  • Create a fake module object in sys.modules and give it a __file__ attribute that points to classes.py.
  • Pass in a namespace to execfile() that sets __name__ to match the fake module object.

Demo:

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

Just to make it explicit, creating a copy of globals() only prevents execfile() from modifying your current module namespace (or your REPL namespace). There is otherwise no difference between the dictionaries being passed to execfile().

Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
  • In fact, it does not. If you create a second file, and call `execfile('classes.py', globals())` you will find that it raises an error. – Lennart Regebro Jul 04 '13 at 12:47
  • Sorry, Martijn, I believe you are incorrect. The problem is when the namespace it's executed in is not the `globals()` of the class module. This happens if you run it in the interpreter, or execfile it and pass in a namespace. But if you do not pass in a namespace, it works. – Lennart Regebro Jul 04 '13 at 12:49
  • @LennartRegebro: I did **not** pass in a namespace. I tested this with PDB and ran through the various scenarios. Providing a namespace always sets `ParentModel.__module__` to `__builtin__`, whatever you set in that namespace. – Martijn Pieters Jul 04 '13 at 12:52
  • @LennartRegebro: I included the `test.py` script to test this. – Martijn Pieters Jul 04 '13 at 12:53
  • @LennartRegebro: Python 2.7.5 here, on Mac. – Martijn Pieters Jul 04 '13 at 12:54
  • let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/32888/discussion-between-lennart-regebro-and-martijn-pieters) – Lennart Regebro Jul 04 '13 at 12:56
  • Yes, but on different platforms. There is some funny edge-case here, clearly. It's also to some extent moot, as `execfile()` is gone in Python 3. – Lennart Regebro Jul 04 '13 at 13:18
  • Its gone !?!?! Surely not without replacement ? – dilbert Jul 04 '13 at 13:25
  • @Martijn Pieters, just to confirm, the inspect operates on a module level and using execfile sidesteps this? – dilbert Jul 04 '13 at 13:26
  • @dilbert: Yeah, exec(open(filename)), which works in Python 2 as well. – Lennart Regebro Jul 04 '13 at 13:26
  • @dilbert: The replacement is to use `compile()` yourself then `exec()` on the compiled object. Or pass the open file object to `exec()`. – Martijn Pieters Jul 04 '13 at 13:27
  • @dilbert: `inpect()` looks for the module on the inspected class, and when using `execfile` that module name is inherited from the current namespace. – Martijn Pieters Jul 04 '13 at 13:28
  • But as per the example, passing execfile a namespace with __name__ defined overrides this? – dilbert Jul 04 '13 at 13:35
  • @dilbert: `__name__` is the name of the module the code *thinks* is being used. – Martijn Pieters Jul 04 '13 at 13:41
1

This issue isn't that dict(globals()) != globals(), because it is. The issue here is that the globals() in the context you execute execfile() is not the same globals() as in your classes.py.

When you pass in a namespace you replace the namespace that would otherwise be created. If you pass in a namespace, you'll get one created for you for the classes module. This means that __main__ will be the classes.py file. However, when you pass in the namespace of the file where execfile() is called, that namespace will be used instead, and __main__ will be that module.

That leads to inspect failing to find the class definition in the source code, because it's looking in the wrong file.

If you pass in an empty namespace, it will not find a __main__ at all, and the class will be assumed to be a builtin, with no source available, and an error to that effect will be raised.

In summary, the mistake you make here is to think of globals() and being global to the interpreter, when it's in fact global to the module.

From discussions with Marjtin, it's clear that things work slightly differently on OS X. That means you can not rely on this working even if you don't pass in a namespace.

And that then leads to the question of why you are doing this, and what you are actually trying to achieve.

Lennart Regebro
  • 167,292
  • 41
  • 224
  • 251
  • I'm trying to achieve something awfully, awfully dodgy. But the help is much appreciated. – dilbert Jul 04 '13 at 13:16
  • @dilbert: The point being is that there probably is a better way. – Lennart Regebro Jul 04 '13 at 13:17
  • Well, I understand its best to go for approaches more congruent with the system you're operating under, but this is a bit different. I'm intending to extend upon: http://stackoverflow.com/questions/16907186/python-model-inheritance-and-order-of-model-declaration. – dilbert Jul 04 '13 at 13:22
  • @dilbert: Well, as long as you do it for fun and learning. :-) – Lennart Regebro Jul 04 '13 at 13:30