views:

402

answers:

4

Is there a way to set up a global variable inside of a module? When I tried to do it the most obvious way as appears below, the Python interpreter said the variable __DBNAME__ did not exist.

...
__DBNAME__ = None

def initDB(name):
    if not __DBNAME__:
        __DBNAME__ = name
    else:
        raise RuntimeError("Database name has already been set.")
...

And after importing the module in a different file

...
import mymodule
mymodule.initDB('mydb.sqlite')
...

And the traceback was: UnboundLocalError: local variable '__DBNAME__' referenced before assignment

Any ideas? I'm trying to set up a singleton by using a module, as per this fellow's recommendation.

+2  A: 

This is a very basic question, you should do your homework a bit. Please see: http://docs.python.org/reference/simple%5Fstmts.html#the-global-statement

Antoine P.
You're right it is, and I should have done a bit more homework, but thank you regardless. I guess I was thinking that it was more complicated than a global variable.
daveslab
I'd argue this is a valid question for StackOverflow. It may be an "easy" question for someone familiar with Python and with the Python docs, but may be more challenging for someone new to the language who hasn't read the language reference back to front; regardless, the community has repeatedly affirmed that questions like this are part of the reason SO was created. The question is well phrased, has a good title, and contains code providing a clear indication of what the OP has done to date so that we can answer succinctly.
Jarret Hardie
@Jarret, yes, I've definitely gotten the impression that RTFM is not an acceptable answer, though a link to the appropriate manual page is, though less useful than a full explanation.
Omnifarious
I'm not sure what you're arguing about here. The "global" statement is something as basic as the "for" loop. Anybody should be able to read about it in the tutorial or the official docs without having to ask for it here.Giving a full-size response to such a trivial answer is *not* making the OP a service, since he *has* to be able to acquire that kind of knowledge on his own if he wants to be a decent programmer.
Antoine P.
+3  A: 

For this, you need to declare the variable as global. However, a global variable is also accessible from outside the module by using module_name.var_name. Add this as the first line of your module:

global __DBNAME__
Chinmay Kanchi
is there any way to make it accessible to the whole module, but not available to being called by module_name.__DBNAME__?
daveslab
Yes... you can put the global statement inside your function to make it "global" within the module (within that function... you'd have to repeat the global declaration in every function that uses this global). For example (forgive the code in comments): `def initDB(name):\n global __DBNAME__`
Jarret Hardie
Thanks, Jarret. Unfortunately, when I try that, and run dir(mymodule) on the console, it shows the variables as available and I can access them. Am I misunderstanding you?
daveslab
Remember, in Python `_DBNAME` (single underscore) is considered to be a private variable by convention. This is only semi-enforced for classes and not at all for "naked" code, but most decent programmers will treat `_var` as private.
Chinmay Kanchi
Thats true, @cgkanchi, but I'd like to see if it's strictly enforceable.
daveslab
Put the whole thing in a class. That way, at least someone who wants to access the private variable has to do some work.
Chinmay Kanchi
It's not enforceable daveslab. The idea in Python is that we're all adults and that private and protected variables are best accomplished by contract and convention that any strict compiler-enforced mechanism.
Jarret Hardie
Fair enough, Jarret, that's what I figured. And thanks @cgkanchi for the idea, probably will go with that.
daveslab
+4  A: 

Here is what is going on.

First, the only global variables Python really has are module-scoped variables. You cannot make a variable that is truly global; all you can do is make a variable in a particular scope. (If you make a variable inside the Python interpreter, and then import other modules, your variable is in the outermost scope and thus global within your Python session.)

All you have to do to make a module-global variable is just assign to a name.

Imagine a file called foo.py, containing this single line:

X = 1

Now imagine you import it.

import foo
print(foo.X)  # prints 1

However, let's suppose you want to use one of your module-scope variables as a global inside a function, as in your example. Python's default is to assume that function variables are local. You simply add a global declaration in your function, before you try to use the global.

def initDB(name):
    global __DBNAME__  # add this line!
    if __DBNAME__ is None: # see notes below; explicit test for None
        __DBNAME__ = name
    else:
        raise RuntimeError("Database name has already been set.")

By the way, for this example, the simple if not __DBNAME__ test is adequate, because any string value other than an empty string will evaluate true, so any actual database name will evaluate true. But for variables that might contain a number value that might be 0, you can't just say if not variablename; in that case, you should explicitly test for None using the is operator. I modified the example to add an explicit None test. The explicit test for None is never wrong, so I default to using it.

Finally, as others have noted on this page, two leading underscores signals to Python that you want the variable to be "private" to the module. If you ever do an import * from mymodule, Python will not import names with two leading underscores into your name space. But if you just do a simple import mymodule and then say dir(mymodule) you will see the "private" variables in the list, and if you explicitly refer to mymodule.__DBNAME__ Python won't care, it will just let you refer to it. The double leading underscores are a major clue to users of your module that you don't want them rebinding that name to some value of their own.

It is considered best practice in Python not to do import *, but to minimize the coupling and maximize explicitness by either using mymodule.something or by explicitly doing an import like from mymodule import something.

steveha
"any string value will evaluate true": an empty string (ie: `s = ''`) will also evaluate to false. Otherwise +1
Jarret Hardie
Yeah, I didn't phrase that well. I meant "any actual database name". I'll edit the text to fix that mistake.
steveha
Great answer, @steveha, summarizing all right info so far and still giving the answer.
daveslab
A: 

You are falling for a subtle quirk. You cannot re-assign module-level variables inside a python function. I think this is there to stop people re-assigning stuff inside a function by accident.

You can access the module namespace, you just shouldn't try to re-assign. If your function assigns something, it automatically becomes a function variable - and python won't look in the module namespace.

You can do:

__DB_NAME__ = None

def func():
    if __DB_NAME__:
        connect(__DB_NAME__)
    else:
        connect(Default_value)

but you cannot re-assign __DB_NAME__ inside a function.

One workaround:

__DB_NAME__ = [None]

def func():
    if __DB_NAME__[0]:
        connect(__DB_NAME__[0])
    else:
        __DB_NAME__[0] = Default_value

Note, I'm not re-assigning __DB_NAME__, I'm just modifying it's contents.

wisty