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 theuser
module and load it- Python does not have a builtin
user
module, so that it searches for it in itssys.path
directories, and find a matching file - Python starts running the
user.py
file, which defines aUser
class, so it instantiates aclass
instance for it (with a static member_client
set toNone
), and binds it to theUser
name in the current module - then it prints that
User
's static member_client
is indeedNone
- having finished running the
user.py
file, Python resumes running theclient.py
file
- Python does not have a builtin
- the import succeeded, so Python has a
module
object and binds it to theuser
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 theclient
name in the current module (namedclient
too) - it then calls
authenticate
, which setsuser
(the name of the variable pointing to the module loaded fromuser.py
).User
(the class in the module)._client
(the static variable in the class) toself
- 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.