2

Is it possible in Python to have two objects, each of them an instance of a different class, that always share the same value for a particular attribute?

For instance, suppose the following code:

class A():
    def __init__(self):
        self.attrA = "attrA"
        self.grid = None

    def init_grid(self, x):
        self.grid = x

class B():
    def __init__(self):
        self.attrB = "attrB"
        self.grid = None

    def init_grid_as_preexisting(self, pre_grid):
        self.grid = pre_grid


a = A()
a.init_grid([1,2,3])

b = B()
b.init_grid_as_preexisting(a.grid)

print("a.grid, b.grid = ", a.grid, b.grid)
# OUT: a.grid, b.grid =  [1, 2, 3] [1, 2, 3]

a.grid = [4,5,6]

print("a.grid, b.grid = ", a.grid, b.grid)
# OUT: a.grid, b.grid =  [4, 5, 6] [1, 2, 3]

b.grid = [7,8,9]

print("a.grid, b.grid = ", a.grid, b.grid)
# OUT: a.grid, b.grid =  [4, 5, 6] [7, 8, 9]

Here my goal would be for a.grid and b.grid to contain the same value always, regardless of whether one is initialized from the other or which one is modified; the desired output would be then:

a.grid, b.grid =  [1, 2, 3] [1, 2, 3]
a.grid, b.grid =  [4, 5, 6] [4, 5, 6]
a.grid, b.grid =  [7, 8, 9] [7, 8, 9]

In this question it is suggested to use a base class containing a class attribute, and use a static method to modify the desired shared attribute. I would rather not use this solution as I don't want to have this attribute shared among all instances always, only when it is strictly desired. From this other question, I guess I could use the Mutable Default Argument property to have a shared value for a given parameter, but again, I don't always want the parameter to be shared.

In short, is it possible to have two objects, each an instance of two different classes, to have a shared parameter?

3 Answers3

2

You can have a parent class to hold the data:

class GridHolder:
    def __init__(self):
        self.grid = None


class A:

    def __init__(self, grid_holder: GridHolder):
        self.attrA = "attrA"
        self.grid_holder = grid_holder

    @property
    def grid(self):
        return self.grid_holder.grid

    @grid.setter
    def grid(self, value):
        self.grid_holder.grid = value


class B:
    def __init__(self, grid_holder: GridHolder):
        self.attrB = "attrB"
        self.grid_holder = grid_holder

    @property
    def grid(self):
        return self.grid_holder.grid

    @grid.setter
    def grid(self, value):
        self.grid_holder.grid = value

data_holder = GridHolder()

a = A(data_holder)
a.grid = [1, 2, 3]

b = B(data_holder)

print("a.grid, b.grid = ", a.grid, b.grid)
# OUT: a.grid, b.grid =  [1, 2, 3] [1, 2, 3]

a.grid = [4, 5, 6]

print("a.grid, b.grid = ", a.grid, b.grid)
# OUT: a.grid, b.grid =  [4, 5, 6] [1, 2, 3]

b.grid = [7, 8, 9]

print("a.grid, b.grid = ", a.grid, b.grid)
# OUT: a.grid, b.grid =  [4, 5, 6] [7, 8, 9]

Output:

a.grid, b.grid =  [1, 2, 3] [1, 2, 3]
a.grid, b.grid =  [4, 5, 6] [4, 5, 6]
a.grid, b.grid =  [7, 8, 9] [7, 8, 9]
Tom McLean
  • 5,583
  • 1
  • 11
  • 36
  • 1
    To be clear, You do not need to inherit from child in my example, I have just used it so I dont need to repeat the property and property setter code. – Tom McLean Sep 12 '22 at 18:24
  • 2
    @cards Agreed, refactored. There are many ways to do it, I think this way is easy but I dont really like the idea of making a class just to hold a value. – Tom McLean Sep 12 '22 at 18:55
  • 1
    @juanpa.arrivillaga I did do that but the question stated that the classes were separate. Ill edit it back in – Tom McLean Sep 12 '22 at 19:01
1

You can have the B instance refer to the A instance and use properties to refer to the grid attribute:

class A():
    def __init__(self):
        self.attrA = "attrA"
        self.grid = None

    def init_grid(self, x):
        self.grid = x

class B():
    def __init__(self, a):
        self.attrB = "attrB"
        self.a = a

    @property
    def grid(self):
        return self.a.grid 

    @grid.setter
    def grid(self, value):
        self.a.grid = value

a = A()
a.init_grid([1,2,3])

b = B(a)

print("a.grid, b.grid = ", a.grid, b.grid)
# OUT: a.grid, b.grid =  [1, 2, 3] [1, 2, 3]

a.grid = [4,5,6]

print("a.grid, b.grid = ", a.grid, b.grid)
# OUT: a.grid, b.grid =  [4, 5, 6] [1, 2, 3]

b.grid = [7,8,9]

print("a.grid, b.grid = ", a.grid, b.grid)
# OUT: a.grid, b.grid =  [4, 5, 6] [7, 8, 9]

Output as requested

quamrana
  • 37,849
  • 12
  • 53
  • 71
  • 1
    In this code, every instance of class 'A' and 'B' will share the same data, where I believe OP wants different instances of 'A' and 'B' to not necessarily hold the same data – Tom McLean Sep 12 '22 at 18:26
  • Indeed, this solution works most of the times, however I was looking for a solution that also allows for the possibility of not sharing the attribute. – Álvaro F. b.f. Sep 12 '22 at 19:45
  • What happened to: `"contain the same value always,"`? – quamrana Sep 12 '22 at 19:52
  • @quamrana I meant "contain the same value always" if wanted for a given set of instances, but with the possibility of also having instances that do not necessarily share the same value if wanted (as is usually the case). – Álvaro F. b.f. Sep 12 '22 at 20:00
  • 1
    But you just need a factory for the other instances: `def make_plain_B(): return B(A())` and all the clients will never know. – quamrana Sep 12 '22 at 20:07
  • 1
    @TomMcLean: In my code you have to specifically make a particular `B` refer to a particular `A`. You can have lots of `A`s which don't share their data and with the factory above, you can have particular `B`s refer to 'private' `A`s. – quamrana Sep 12 '22 at 20:12
  • @quamrana I understand what you mean now. However I still see the problem that it is not possible to initialize a `B` without passing in an `A`; in other words, any `B` must be tied to an `A` and cannot exist on its own. It would that `A` is higher in the hierarchy than `B`, when they should be at the same level. – Álvaro F. b.f. Sep 13 '22 at 06:38
  • Although you don’t need an ‘A’ specifically, only an object with the right attribute: ‘grid’ in your case. – quamrana Sep 13 '22 at 07:01
0

You could make use of a class variable and using .grid as property, A and B inherit from grid the properties only:

class grid:
    _grid = None

    @property
    def grid(self):
        return grid._grid

    @grid.setter
    def grid(self, value):
        grid._grid = value


class A(grid):
    def __init__(self):
        self.attrA = "attrA"


class B(grid):
    def __init__(self):
        self.attrB = "attrB"


a = A()
a.grid = [1, 2, 3]

b = B()
print("a.grid, b.grid = ", a.grid, b.grid)
a.grid = [4, 5, 6]
print("a.grid, b.grid = ", a.grid, b.grid)
b.grid = [7, 8, 9]
print("a.grid, b.grid = ", a.grid, b.grid)

Out:

a.grid, b.grid =  [1, 2, 3] [1, 2, 3]
a.grid, b.grid =  [4, 5, 6] [4, 5, 6]
a.grid, b.grid =  [7, 8, 9] [7, 8, 9]
Maurice Meyer
  • 17,279
  • 4
  • 30
  • 47
  • 1
    In this code, every instance of class 'A' and 'B' will share the same data because it is using a class variable, where I believe OP wants different instances of 'A' and 'B' to not necessarily hold the same data – Tom McLean Sep 12 '22 at 18:26