How to access/modify the same instance of a class across multiple modules?

I am struggling to access/modify the same instance of a class across multiple modules. Which software design pattern should I use to have such an ability?

Without knowing what you are doing, it is hard to say what you should be
doing instead. Have you tried just importing the module that created the
instance, and accessing the instance that way?

# Module A.py
instance = SomeClass(args)
instance.attribute = "initial value"

# Module B.py
import A
A.instance.attribute = "modified by B"

# Module C.py
import A
A.instance.attribute = "modified by C"

Everything in Python is an instance of some class, including
functions. If you can do something like this:

import math
math.sqrt(x)

from two different modules, then you can access the same instance of a
class across multiple modules. And if you can access the instance, then
you can mutate it (if it is a mutable object).

Can you show a minimal reproducible example of code that fails?

My guess is that you are doing something like this.

# Module B.py
from A import instance
instance = "this is new"

# Module C.py
from A import instance
assert instance == "this is new"  # fails

Am I close?

Do you understand the difference between these?

  1. mutating an object;

  2. rebinding a name to a different object.

You are not just close, your guess is spot on!

Also, I am not entirely sure I understand a difference between mutating an object and rebinding a name to a different object. Please elaborate on the difference.

@steven.daprano, are you implying I should use mutable objects to do what I want?

Hi Boštjan,

Binding, or rebinding, an object to a name is also called assignment:

x = 999

binds the value (an object) 999 to the name x. Python also has a handful
of other operations which are considered name binding:

import sys
for i in (1, 2, 4, 8): ...

and a few others, but plain old = is the main one.

However, this only applies to assignment to a bare name. Assignment to a
dotted name or a subscript:

x.attribute = value
x[index] = value

is an in-place modification.

Mutation takes a value, such as a list, and modifies it in-place without
replacing it with a new list. So:

L = []  # Bind the value, an empty list, to the name L.
L = [1]  # Re-bind a new value to the name L.
# At this point, the old empty list is free to be reclaimed
# by the garbage collector, freeing the memory used.

L.append(2)  # Modify the list in place.

Only some objects can be modified in place. They are called mutable
objects.

Other objects are immutable, and cannot be modified in place. This
includes ints, floats, strings and tuples (but not necessarily the
contents of the tuple).

How this relates to you is that when you import a name from another
module:

# in B.py
from A import L

that creates a new name binding (variable) in the B module, which is
separate from the name binding in the A module.

So assignment in B doesn’t touch the variable in A:

# in B.py
L = []  # replaces the L variable in B, but not in A

since A and B are different namespaces. But mutating the value
(modifying it) affects both modules, since the value is shared and not a
copy:

from A import L

The value of L, an object, is shared between the A namespace and B
namespace; the name L is independent in the two namespaces.

# This mutates the object, so the change is seen in both A and B
L.append(1)

# This rebinds the name L to a new value, so is only seen in the
# current namespace, not the original.
L = [1, 2]

If you want the assignment to be seen in both modules, you have to use
dotted names:

import A
A.L.append(1)  # Mutation is always visible everywhere.
A.L = [1, 2]  # Rebinding takes place in the A namespace.
assert A.L = [1, 2]

This applies to immutable objects as well:

A.x = 999  # Rebinding takes place in the A namespace. 
assert A.x == 999

Does that help?

You can use mutable objects, if you wish, or you can import the module
and use dotted names instead.

@steven.daprano, yes, this helps. Thank you.

While waiting for your answer, I’ve read an article about Object-Oriented Programming (OOP).

Suppose that in a class definiton (in __init__) we defined self.my_var = 0 and then an instance of a class has changed the value of our my_var instance variable to 1).

The changed value (the value of my_var when it is 1) can only be retrieved by the same instance of an object that changed the value of my_var, because values of such variables (i.e., instance variables) are not shared between instances of an object.

On the other hand, changing the value of a class variable, however, is shared between instances of an object, but the article says that such an approach breaks inheritance.

So, I’m kind of baffled as to what is the Pythonic way to go about it.

@steven.daprano, please check my code here: GitHub - PedanticHacker/SuperChess: A GUI application for playing chess.

Look at how I’m currently managing my data by importing instances in modules (rather than class definitions). I find it strange that I have to do this, otherwise I don’t get the behaviour I want.

Please test my code and play around. If you can squeeze in some of your time by doing a code review, it would rock my world.

However, this only applies to assignment to a bare name. Assignment to a dotted name or a subscript:

x.attribute = value

is an in-place modification.

Well, yes and no. It is a modification of the value assigned to x in this case, but it is a re-binding or the name attribute in the x object’s namespace.

So we have multiple concepts to grok here:

  • rebinding vs mutating

  • namespaces.

A lot of things in Python are namespaces: modules, classes, class instances, …

In this case, the OP is asking about instances in other modules. Well, a module is a (mutable) namespace, so if you rebind a name in a module, you are rebinding a name, regardless of where you do it from. For example:

in a.py:

x = 5

in b.py:

import a
a.x = 10

in c.py:

import a
print(x)

you will get either 5 or 10, depending on when b.py and c.py are run.

Sorry – that’s a little too much code for a quick glance.

Perhaps point us to an example of the issue you are wondering about.

But since you asked, I did notice this:



class ChessOpenings:
    """Create a storage for chess openings."""

    def storage(self):
        ...
        # [big dict hard-coded here]

That’s a quite unnecessary class. Python does not need a class as a placeholder for anything. And by hard coding that dict in the storage method, you’re actually making a new one every timethat method is called. As it’s apparetnly static, that’s totally unneccesy.

Maybe jsut a simple value in the module:

CHESS_OPENINGS = {put the big dict here}

It’s a convention to use ALL_CAPS for constants, which this appears to be, but you can call it anything.