Search code examples
pythonimportmoduleglobal-variables

Python - Optimal way to re-assign global variables from function in other module


I have a module which I called entities.py - there are 2 classes within it and 2 global variables as in below pattern:

FIRST_VAR = ...
SECOND_VAR = ...

class FirstClass:
    [...]

class SecondClass:
    [...]

I also have another module (let's call it main.py for now) where I import both classes and constants as like here:

from entities import FirstClass, SecondClass, FIRST_VAR, SECOND_VAR

In the same "main.py" module I have another constant: THIRD_VAR = ..., and another class, in which all of imported names are being used.

Now, I have a function, which is being called only if a certain condition is met (passing config file path as CLI argument in my case). As my best bet, I've written it as following:

def update_consts_from_config(config: ConfigParser):
    global FIRST_VAR
    global SECOND_VAR
    global THIRD_VAR
    FIRST_VAR = ...
    SECOND_VAR = ...
    THIRD_VAR = ...

This works perfectly fine, although PyCharm indicates two issues, which at least I don't consider accurate.

from entities import FirstClass, SecondClass, FIRST_VAR, SECOND_VAR - here it warns me that FIRST_VAR and SECOND_VAR are unused imports, but from my understanding and testing they are used and not re-declared elsewhere unless function update_consts_from_config is invoked.

Also, under update_consts_from_config function:

global FIRST_VAR - at this and next line, it says Global variable FIRST_VAR is undefined at the module level

My question is, should I really care about those warnings and (as I think the code is correct and clear), or am I missing something important and should come up with something different here?

I know I can do something as:

import entities
from entities import FirstClass, SecondClass

FIRST_VAR = entities.FIRST_VAR
SECOND_VAR = entities.SECOND_VAR

and work from there, but this look like an overkill for me, entities module has only what I have to import in main.py which also strictly depends on it, therefore I would rather stick to importing those names explicitly than referencing them by entities. just for that reason

What do you think would be a best practice here? I would like my code to clear, unambiguous and somehow optimal.


Solution

  • Import only entities, then refer to variables in its namespace to access/modify them.

    Note: this pattern, modifying constants in other modules (which then, to purists, aren't so much constants as globals) can be justified. I have tons of cases where I use constants, rather than magic variables, as module level configuration. However, for example for testing, I might reach in and modify these constants. Say to switch a cache expiry from 2 days to 0.1 seconds to test caching. Or like you propose, to override configuration. Tread carefully, but it can be useful.

    main.py:

    import entities
    
    def update_consts_from_config(FIRST_VAR):
        entities.FIRST_VAR = FIRST_VAR
    
    firstclass = entities.FirstClass()
    
    print(f"{entities.FIRST_VAR=} before override")
    firstclass.debug()
    entities.debug()
    
    update_consts_from_config("override")
    
    print(f"{entities.FIRST_VAR=} after override")
    firstclass.debug()
    entities.debug()
    

    entities.py:

    FIRST_VAR = "ori"
    
    class FirstClass:
        def debug(self):
            print(f"entities.py:{FIRST_VAR=}")
    
    
    def debug():
        print(f"making sure no closure/locality effects after object instantation {FIRST_VAR=}")
    

    $ python main.py

    
    entities.FIRST_VAR='ori' before override
    entities.py:FIRST_VAR='ori'
    making sure no closure/locality effects after object instantation FIRST_VAR='ori'
    entities.FIRST_VAR='override' after override
    entities.py:FIRST_VAR='override'
    making sure no closure/locality effects after object instantation FIRST_VAR='override'
    

    Now, if FIRST_VAR wasn't a string, int or another type of immutable, you should I think be able to import it separately and mutate it. Like SECOND_VAR.append("config override") in main.py. But assigning to a global in main.py will only affect affect the main.py binding, so if you want to share actual state between main.py and entities and other modules, everyone, not just main.py needs to import entities then access entities.FIRST_VAR.

    Oh, and if you had:

    class SecondClass:
    
       def __init__(self):
          self.FIRST_VAR = FIRST_VAR
    

    then its instance-level value of that immutable string/int would not be affected by any overrides done after an instance creation. Mutables like lists or dictionaries would be affected because they're all different bindings pointing to the same variable.

    Last, wrt to those "tricky" namespaces. global in your original code means: "dont consider FIRST_VAR as a variable to assign in update_consts_from_config s local namespace , instead assign it to main.py global, script-level namespace".

    It does not mean "assign it to some global state magically shared between entities.py and main.py". __builtins__ might be that beast but modifying it is considered extremely bad form in Python.