Importing variable directly yields a different val

2019-01-20 18:33发布

问题:

I have three modules:

  • constants, which contains loaded configuration and other stuff in a class
  • main, which initializes constants when it is run
  • user, which imports constants and accesses its configuration.

constants module (simplified) looks like this:

class Constants:
    def __init__(self, filename):
        # Read values from INI file
        config = self.read_inifile(filename)
        self.somevalue = config['ex']['ex']

    def read_inifile(self, filename):
        # reads inifile

constants: Constants = None

def populate_constants(filename):
    global constants
    constants = Constants(filename)

A simple object which should hold the configuration, nothing groundbreaking.

The function populate_constants() is called from main on program startup.

Now the weirdness happens - when I import the constants module from user like this, the constants is None:

from toplevelpkg.constants import constants
print(constants)

None

However, if I import it like this, constants is initialized as one would expect:

from toplevelpkg import constants
print(constants.constants)

<trumpet.constants.Constants object at 0x035AA130>

Why is that?

EDIT: in my code, the function attempting to read constants is run asynchronously via await loop.run_in_executor(None, method_needing_import). Not sure if this may cause issues?

(and as a side-question, is it a good practice to have a configuration-holding object which parses the config file and provides it as its member variables?)

回答1:

There's indeed a difference between

from mymodule import obj
(...)
do_something_with(obj)

and

import mymodule
(...)
do_something_with(mymodule.obj)

In the first case, it acts as :

 import mymodule
 obj = mymodule.obj
 del mymodule

which means that at this point, in the current module, obj is a "global" (which in python actually means 'module-level', not 'application-wide') name bound to whatever mymodule.obj was when it was imported (in your case : None). From then on, mymodule.obj and the module-local obj names live in different namespaces (the first one in mymodule namespace, the second one in the current module namespace), and rebinding mymodule.obj from anywhere won't change anything to what the current module's obj is bound to. Actually, it's exactly as if you were doing this:

a = 2
b = a
a = 4

After the third statement, b is obviously still bound to 2 - rebinding a to 4 doesn't impact b.

In the second case (import mymodule) what get's bound in the importing module's namespace is the whole mymodule object, so if mymodule.obj gets rebound (from within mymodule or anywhere else) the change will be visible in the importing module. In this case it's equivalent to

a = {"x": 2}
b = a
a["x"] = 4

In which case the change will be visible from b["x"] as well since a and b are still bound to the very same object.

wrt/ your side question: yes, having some "config" object is quite a common pattern. You just may want to make sure you can build it "from scratch" too (I mean, not necessarily from a config file) to make unittesting easier.