Leonardo
Leonardo

Reputation: 1522

Understanding beavhiour change between: import modname -> and -> from modname import membername

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

Answers (2)

bruno desthuilliers
bruno desthuilliers

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

cjfro
cjfro

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

Related Questions