2

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.

Matthew Ward
  • 351
  • 3
  • 12

3 Answers3

3

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.

Community
  • 1
  • 1
sotapme
  • 4,695
  • 2
  • 19
  • 20
  • Maybe this is an over optimization, but with Borg doesnt it create a new block in memory for each instance of the 'wrapper' class? Not that this project is that memory based, just doesnt necessarily seem like the ideal solution. It seems the whole concept of a singleton is not pythonic, so what IS the pythonic approach to it? (Borg kinda seems like code smell from a limitation of the language). (Forgive my herecy) – Matthew Ward Feb 19 '13 at 21:44
  • So with calling main... does that mean that call that initialized the object has to be called first? As in if a module refernce the module with main(), but main wasnt called the value of app is None, but shouldnt the subsequent call reference the global app and set it to not None? So to clairfy, I do call python app.py, app.py imports resources.py, resources references app for the instance of the object. – Matthew Ward Feb 19 '13 at 21:45
  • @MatthewWard: Yes, using either a borg or a singleton is somewhat unpythonic. But that's because they're global objects. Asking "how do I create a global object that isn't a borg or a singleton" is not the answer—that's just asking how to hide your code smell instead of fixing it. If you have an object that you can pass around as a function parameter, instance member, etc., do that. If you can't do that for whatever reason, then by definition you need a global. – abarnert Feb 19 '13 at 22:06
  • @MatthewWard: For your second comment: You've pretty much got it, but my answer clarifies a bit if you're not clear. – abarnert Feb 19 '13 at 22:08
  • Well I tried the borg thing. It sort of works. Not all instances end up with the shared state - not even sure how that is possible. There are so many things about this language I am starting to hate. – Matthew Ward Feb 20 '13 at 01:23
  • I know that when I was looking at Borgs there were some dodgy implementations that didn't seem to work. If it helps you could frame your question in terms of another language like *In Java I'd use dependency injection how do I do that in Python* see if that gives a satisfactory answer. – sotapme Feb 20 '13 at 15:03
0

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.

abarnert
  • 354,177
  • 51
  • 601
  • 671
0

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)
oleron
  • 483
  • 2
  • 7