-1

In a program I have multiple instances of a particular class with a Func attribute as attributes within another class. The Func attribute is a string which is the name of a function in the parent class. The parent class has a function that I want to access a particular instance of the child class and execute the function associated with it, which updates a variable within the parent class.

I have tried the following (minimal example):

class a:
    def __init__(self):
        self.func = "bar"

class foo:
    def __init__(self):
        self.val = 0
        self.e = a()

    def bar(self):
        self.val += 1

    def baz(self):
        a = getattr(globals()["foo"](), self.e.func)
        a()
    

Creating an instance of foo and running foo.baz causes foo.val to remain at 0, which is not what I want to happen. How do I make it so that self.val is updated?

dmitryro
  • 3,463
  • 2
  • 20
  • 28
baofu
  • 1
  • 2
    Replace `getattr(globals()["foo"](), self.e.func)` with `getattr(self, self.e.func)` ? – bb1 Mar 05 '23 at 01:35
  • 1
    I agree with @bb1. Just to elaborate a bit, `globals()["foo"]()` is creating a new instance of the foo class. Instead, we use `self` to perform the attribute lookup on the current instance of foo. – purple Mar 05 '23 at 01:37
  • Welcome to Stack Overflow. Doing `globals()["foo"]` is pointless; it means the same as `foo`. If we substitute the known value for `self.e.func` (which is the same for all instances), we can see the problem much more clearly: `getattr(foo(), 'bar')` will obviously do the same thing that `foo().bar` does, which is not the same thing that `self.bar` does. For future questions, please try to [reason about](https://ericlippert.com/2014/03/05/how-to-debug-small-programs/) the code first, and try to understand what happens in each step; we don't provide a debugging service. – Karl Knechtel Mar 05 '23 at 01:42
  • For example: given the problem "self.val" is not updated, the next thing to check is "is self.bar called?" Python comes with a debugger in the standard library, called `pdb`; you can use this to check what code gets called, which instances are used for method calls etc. – Karl Knechtel Mar 05 '23 at 01:44
  • We can also approach problems by reasoning forwards from the code, and verifying our expectations and thought process. For example, where the code says `globals()["foo"]()`, exactly what did you expect this to mean? Does it make sense to try to `getattr` from that result? Why or why not? (I think that this kind of reasoning should find a problem like this for you easily, since the rest of the code shows you clearly *do* understand `self`, and more generally the idea of separate instances of a class.) – Karl Knechtel Mar 05 '23 at 01:46

1 Answers1

0

What you want is a class variable. Helpfully, because its usually the desired behavior, if you were to mutate a class attribute from a method, python will create a copy of that value on the instance before mutating it (which defeats your purpose). Knowing that though, there are ways to get what you want.

class Parent:
  val = 0
  
  @classmathod
  def update(cls):
    # a classmethod is one that only has references to
    # the class object. Since class objects are singletons,
    # updating properties on it update it everywhere you
    # can see that object
    cls.val += 1
  

class Child:
  def __init__(self):
    # here you called the Parent constructor- so you have
    # an instance of the class        
    self.retained = Parent()
    # classes are objects to, if you wanted a reference to the
    # class object this would work
    self.parent = Parent
    # at this point both self.parent.val and self.retained.val
    # would be the same

 def bar(self):
   self.parent.val += 1
   # This increments the value on the class object. Parent.val
   # and self.parent.val and # self.retained.val would all == 1
   self.parent.update()
   self.retained.update()
   # you can access class methods from the class or from instances
   # of that class. both self.parent.val and self.retained.val == 3
   self.retained.val += 1
   # this is where you were before. Updating a class attribute
   # on an instance will add an instance attribute that starts
   # with the value of the class object, but can be updated separately
   # now self.parent.val == 3 but self.retained.val == 4
   self.parent.val = 2
   # This changes the value on the class object, but the instance
   # property means self.retained.val still is 4
Paul Becotte
  • 9,767
  • 3
  • 34
  • 42