I was playing with examples in order to answer a question posted here on SO and found hard to understand the mechanics through which python's import *
messes up the scope.
First a bit of context: this question does not deal with practical issue; I understand well that from foo import *
is frowned upon (rightly so) and I grasp that it is for reasons deeper than clarity in the code. My interest here is in understanding the mechanics that causes bad behaviour with circular import *
s. In other words, I understand that the observed behaviour is expected; I don't understand why.
The situation I'm not able to understand are the problems that arise when having, from an imported module (b
), a reference to the importing module (a
), using *
. I managed to observe subtle differences in behaviour when the importing module uses *
or not, but the overall (bad) behaviour is the same. I couldn't find any clear explanation neither in the documentation nor on SO.
Investigating the behaviour through what is available on the scope, I managed to build a small example that illustrates the differences in its content based on the above mentioned question and a few searches I did here in SO and elsewhere. I try to demonstrate as concisely as I can. All code and experiments below were done with python 2.7.8.
Working scenarios
First a trivial module containing a trivial module containing one class, a.py
:
class A:
pass
A first variant of client code, importing module a, b_v1.py
:
from pprint import pprint
def dump_frame(offset=0):
import sys
frame = sys._getframe(1+offset)
d = frame.f_globals
d.update(frame.f_locals)
return d
print 'before import v1'
pprint (dump_frame())
import a
print 'after import v1'
pprint (dump_frame())
print a.A()
Second variant of the same code, importing *
from module a
, b_v2.py
:
from pprint import pprint
def dump_frame(offset=0):
import sys
frame = sys._getframe(1+offset)
d = frame.f_globals
d.update(frame.f_locals)
return d
print 'before import v2'
pprint (dump_frame())
from a import *
print 'after import v2'
pprint (dump_frame())
print A()
- Running both
b_v1
andb_v2
produce the same output before the import, and both are able to instantiate A, as expected. After the import, however, again, as expected, they differ. I highlight the difference:
b_v1.py
, has in the scope
'a': <module 'a' from '.../stackoverflow/instance/a.py'>
while b_v2.py
does not, but has
'A': <class a.A at 0x...>
Both before and after the import, the scope contains
__builtins__
set to<module '__builtin__' (built-in)>
.Both variants succeed in instantiating
A
.
Not working scenarios
The intriguing behaviour is when changing a.py
to contain a circular reference to b
(in both the b_v1
and b_v2
variants).
Adjusted code of a.py
:
from b_v1 import *
class A:
pass
(for shortness's sake, only one case of a.py
is shown; obviously in the case of b_v2.py
the import is for this module, not b_v1.py
)
In my observations of the contents of the scope in the scenario with a circular reference, I see:
In both variants, before the import in
a
,__builtins__
is similar to the cases above. After the import, however, it is changed and contains adict
of'ArithmeticError': , 'AssertionError': , 'AttributeError': , ...
which is needlessly long to paste here.
- The changed
__builtins__
is present twice. This I can understand as being consequence of the importing and would probably not happen if the code were inside a function. In variant
b_v2
the modulea
is present in the scope; it is present in variantb_v1
.In both variants, instantiation of
A
fails. Given that in variantb_v1
the module is present in the scope (therefore, I assume was successfully imported), I had expected to be able to instantiateA
. This is not the case. There are differences, however: in caseb_v1.py
, it fails with anAttributeError: 'module' object has no attribute 'A'
and, as forb_v2.py
, failure is aNameError
. In this later case, it is always the same error independently of whether I try to instantiate asA()
(as in the working example) ofa.A()
.
Summarizing my questions:
Through what mechanics a circular
import *
messes up the scope?Why is it not possible to instantiate
A
in the case b_v1, although the module is in the scope?