Changes to static class variable are not visible from another module

I'm writing a Python package for some REST API. To make it easier to manipulate the state of the objects I get from the API, I want to store a static reference to my API client instance. Let's say I have a file user.py:

class User:
    _client: Client = None

So when the client successfully authenticated for the first time, I update the reference:

class Client:
    def authenticate(self):
        ...
        User._client = self

This happens in my client.py file. Now I can successfully access User._client from any function that was declared in client.py, but when I try the same thing from another module, User._client is still None.

Let's say this is my folder structure

└── my_api_client
    ├── __init__.py
    ├── client.py
    ├── search.py
    └── user.py

Then when I call User._client from a function in search.py, I get None instead of a reference to the current client instance.

I'm having trouble creating a minimal working example that demonstrates this issue, so maybe I just messed up something with my imports. I think it would really help if I understood better what exactly happens if I import a class from another module and if references to static attributes of that class are shared between all modules. If this is indeed the case, what am I doing wrong?


Solution 1:

I tried to reproduce you issue, but could not.

# user.py

print("defining class User")


class User:
    _client = None  # removed type annotation here to prevent circular import


print("right now, User._client is " + repr(User._client))
# client.py

import user


class Client:
    def authenticate(self):
        user.User._client = self


print("instantiating a Client")
client = Client()
print("before athenticating, the User._client is " + repr(so70744003_user.User._client))
client.authenticate()
print("before athenticating, the User._client is " + repr(so70744003_user.User._client))

and running the file client.py :

defining class User
right now, User._client is None
instantiating a Client
before athenticating, the User._client is None
before athenticating, the User._client is <__main__.Client object at 0x7fc1816a9b20>

I can give you a detailed breakdown of what happened :

  • Python was given the file client.py so started running the code in the file
  • it starts with import user so Python will search for the user module and load it
    • Python does not have a builtin user module, so that it searches for it in its sys.path directories, and find a matching file
    • Python starts running the user.py file, which defines a User class, so it instantiates a class instance for it (with a static member _client set to None), and binds it to the User name in the current module
    • then it prints that User's static member _client is indeed None
    • having finished running the user.py file, Python resumes running the client.py file
  • the import succeeded, so Python has a module object and binds it to the user name in the current module scope (client)
  • continuing running the file, a class definition is encountered (with a method defined) and it gets binded to the Client name in the current module
  • then it instantiates a Client and binds it to the client name in the current module (named client too)
  • it then calls authenticate, which sets user(the name of the variable pointing to the module loaded from user.py).User(the class in the module)._client(the static variable in the class) to self
  • finally it prints that the static variable new value is actually an instance of Client

In you question's comments, it was suggested to use a global variable. It would change nothing, because your User class is essentially already a singleton (there is only one declared), you just happen to access one of its fields. A singleton's fields can be considered singleton values too.

I don't know what is the problem with your implementation, you did not gave us enough to find the bug. If you want to find it, I recommend to try to reduce it to a Minimal Reproducible Example. Usually in the process, you will find the solution yourself.

I fear it could be caused by mutual imports, such that the state of the variable depends on the order of import statements in every file of the project. It's a reason to try to break cycles, or be careful around them.