Matthew Ward
Matthew Ward

Reputation: 351

How to initialize a python object once and share across other modules?

For the life of me I am finding me python transition to be extremely frustrating. One of the things I am attempting at doing is to initialize a single instance of a class from a configuration dictionary, then access that class in other modules.

The problems I am facing / the approaches I have taken are not working out and am hoping someone could steer in the right 'pythonic' approach.

First off my app can be initialized as part of a twistd plugin, or as a standalone script.

import resource
class App(object):
  _config = None
  _someotherobject = None
  def __init__(self, config):
    self._config = config
    ....

def main():
  global myapp
  myapp = App({}) # Could use help here, how to pass config to it

myapp = None #Global doesnt work as I expect, it doesnt modify this instance, stays as None
if __name__ == '__main__':
  main()


#-----------resource.py
class Foo(object):
  def foo(self):
    app.myapp.somefunction() #NoneType object has no attribute

I have verified the app object is created before the code in the other module is kicked off. I just can't figure out why the 'global' instance in the module doesn't actually do what I expect, also confused as to how to reference the instance from another module.

----- Edit ------ To clarify a couple points, the script is called with python app.py app.py references a module called resources.py which is a bunch of class definitions. In some of the classes, when executed, they reference the 'singleton' instance of app.myapp.

Upvotes: 2

Views: 8544

Answers (3)

abarnert
abarnert

Reputation: 365657

Your biggest problem is the one mentioned by sotapme. If you don't actually run app.py as your top-level script, you don't have any code calling app.main(), and therefore nothing initializing the global.

Beyond that, "how to reference the instance from another module" is very easy, if you know one simple thing:

In Python, globals are namespaced.

To put it more concretely, in your other modules, just import app, then access the global as app.myapp.

Using the __name__ == '__main__' trick, as sotapme explained, means that you can import app as many times as you want without it running the main() function every time.

In particular, when you run this:

python app.py

The Python interpreter will load app.py with its __name__ set to '__main__', so the if statement will trigger, which will cause the (module-level) global variable myapp to get set to App({}).

Now, when resource.py does an import app, its __name__ will be set to app, so the if statement will not trigger, so you will construct a new App and replace the global. From the code in resource.py, you can use app.myapp, and you will be accessing the same object that code in app.py sees as myapp.


You also ask for help to pass config to the App constructor. I'm not sure what your problem is here. You're passing an empty dict as a config. If you have a different dict to pass, just use it. Instead of this:

myapp = App({}) # Could use help here, how to pass config to it

do this:

myapp = App(configdict)

If your problem is knowing how to get that configdict, that depends on where the information comes from.

If you're trying to parse a user-editable config file, the configparser module works fine for traditional .ini-style files, and the docs have links that explain how to handle some other popular formats.

If you're trying to build up config information from a command line, see argparse.

If you want to allow environment variables to interact with either of the above (e.g., a MYAPP_CONFIG might tell your configparser code to load a different config file than normal, or a MYAPP_CACHE_DIR might provide a different default for the --cachedir command-line argument for argparse), you get the values in os.environ, but have to write your own code to do anything with them.

Upvotes: 0

oleron
oleron

Reputation: 483

To clean up a little bit, here is an example:

class App:
    _config = None
    _someotherobject = None

def __init__(self, config):
    self._config = config

def main():
  myapp = App(config) # pass a config object to App

if  __name__ =='__main__':main()

From another app you will have to do the following:

from first_app import App

myapp = App(config)

Upvotes: 0

sotapme
sotapme

Reputation: 4903

Your main will only ever be called as a standalone script not when imported from another module.

if __name__ == '__main__':
  main()

Is the trick to make your modules runnable from the command line.

To prove it

import app
app.main()

Then run your piece of code.

Once you've initialised it app becomes like a singleton, any other module importing it will get that initialised version.

I had a similar problem where I didn't want to access app but wanted modules to be able to say app = MyApp() and still share the same data ( I forget why I wanted it but it might have had to do with wanting it initialised on first use)

I ended up using a Borg instead of a Singleton.

Upvotes: 3

Related Questions