Reputation: 545
(Python 3.6)
Consider a package odd with two modules foo and bar. I want a customer of odd to use a single import for everything in the package, without having to know what's defined in foo and what's defined in bar. So I define an __init__.py with the following:
from .foo import *
from .bar import *
foo.py contains a global variable baz and a function set_baz() that sets the value of baz, as follows:
baz = 12
def set_baz():
global baz
print('Baz was {}'.format(baz))
baz = 13
print('Now it is changed to {}'.format(baz))
When I import odd (from elsewhere) and look at the value of odd.baz, I get what I expect, the original value of 12.
In [2]: import odd
In [3]: odd.baz
Out[3]: 12
But when I run set_baz(), it does not change the value of odd.baz, as expected, but instead changes a different global variable: odd.foo.baz.
In [4]: odd.set_baz()
Baz was 12
Now it is changed to 13
In [5]: odd.baz
Out[5]: 12
In [6]: odd.foo.baz
Out[6]: 13
Apparently there are two unrelated namespaces, one for odd and one for odd.foo, and each namespace defines baz, differently.
How can I accomplish what I want, a single global variable, accessed by customers of odd as odd.baz?
(And yes, I know that I should not be using global variables like this. I have distilled the reasonable goal I am actually trying to accomplish into this silly example with odd.baz that illustrates the underlying problem.)
Upvotes: 2
Views: 72
Reputation: 3361
Python has names, not variables.
If you import odd.foo.baz
in odd.__init__
, it will create a new name odd.__init__.baz
pointing to the same int
object 12
.
If you change the name odd.foo.baz
to point to another int
, 13
, this has no effect whatsoever on the name odd.__init__.baz
.
Such quirks are one of the reasons why globals are considered bad practice (except very special cases).
You can solve the issue by encapsulating baz
in a mutable object, like a dict
:
odd.foo.py
:
GLOBALS = {'baz': 12}
def set_baz():
print('Baz was {}'.format(GLOBALS['baz']))
GLOBALS['baz'] = 13
print('Now it is changed to {}'.format(GLOBALS['baz']))
def get_baz():
return GLOBALS['baz']
To make a name baz
that behaves similar to a int
, you can use werkzeug.local.LocalProxy
:
from werkzeug.local import LocalProxy
baz = LocalProxy(lambda: GLOBALS['baz'])
Example:
>>> test.baz
12
>>> test.set_baz()
Baz was 12
Now it is changed to 13
>>> test.baz
13
>>> test.baz + 4
17
>>> test.baz.__class__
<class 'werkzeug.local.LocalProxy'>
Upvotes: 1