Reputation: 1522
I am posting a compact example of a program of 3 small files, and I want to understand why changing a couple lines makes this difference.
# main.py
from renderer import Renderer
import shared
class Application():
def __init__(self):
self.isRunning = True
self.renderer = Renderer()
self.globalize()
def globalize(self):
shared.app = self
def main():
app = Application()
while ( app.isRunning ):
app.renderer.quit()
print "program end"
if __name__ == "__main__":
main()
# shared.py
app = None
# renderer.py
import shared
class Renderer():
def __init__(self):
pass
def quit(self):
shared.app.isRunning = False
Now, this usage of the file shared.py
gives the Renderer
class access to the Application
class, which has an instance of Renderer
as a member, for whatever diabolical program design I had in mind. And my question is why this access is no longer guaranteed when renderer.py
is changed as follows:
# renderer.py -- ( new )
from shared import app
class Renderer():
def __init__(self):
pass
def quit(self):
app.isRunning = False
The original renderer.py
made the program end, and the later renderer.py
raises an exception, why is this?
renderer.py", line 7, in quit
app.isRunning = False
AttributeError: 'NoneType' object has no attribute 'isRunning'
Upvotes: 1
Views: 322
Reputation: 77912
Python modules are objects and names in the module are attributes of this module. When imported (from the same path) module instances are "stored" in sys.modules
. In the first case, both main
and renderer
share a reference to the same module instance, so when main
rebinds shared.app
this is also visible in renderer
. In the second case, you make app
a local (module local that is) name, so rebinding shared.app
doesn't impact what renderer.app
is bound to. The important point here is that Python's "variables" have very few in common with C variables. The latter are symbolic names for memory addresses, while in Python they are just key->value pairs, the key (name) belonging to a namespace (module namespace, class namespace, function's local namespace). So the
from module import something
line is just shortcut for:
import module
# create a local name 'something'
something = module.something
del module
# at this point both module.something and something are bound to the
# same object, but they are distinct names in distinct namespaces
# rebinds local name 'something'
# now `module.something` and `something` point to dffererent objects
something = object()
FWIW your design is not "diabolical", it's just EvilGlobals all the way. The simple and obvious way to give Renderer
access to the current app
instance is to pass app
to renderer:
class Renderer(object):
def __init__(self, app):
self.app = app
class Application(object):
def __init__(self):
self.renderer = Renderer(self)
Upvotes: 2
Reputation: 222
You can think of your statement
from shared import app
as equivalent to
import shared
app = shared.app
Here, you have two distinct variables. Changing one does not change the other. When the import happens, app
is set to None
. Even if shared.app
changes value, the app
variable in this module will not change its value. When you call app.isRunning
, app is still None
and you get the error.
The first case works as you expect because you are always accessing shared.app
.
Upvotes: 2