Keep local variables in scope when extending a method

Viewed 103

Let's say I have a class TT inheriting from a class T and extending the behaviour of one of its methods, like so:

class T:
    def __init__(self, p, b):
        self.p = p
        self.b = b
        self.fun()
        
    def fun(self):
        a = self.p + self.b
        print(a)
        

class TT(T):
    def __init__(self, p, b):
        super().__init__(p, b)
        
    def fun(self):
        super().fun()
        c = self.p*self.b
        print(c)

What I'd like to have is TT.fun relying on variables defined in T.fun, a in this example. The two obvious things that came to mind are to make T.fun return a, or to make a an instance variable, but I don't find either satisfactory. Is there a way for TT.fun to share the scope of the method it extends?


EDIT

Thanks for the answers.

To expand on my use case, my derived classes run some sort of optimisation of a vector against a dataset. The parent class is here to suggest a common, coherent interface to the different optimisations. In a class' function to be optimised, some steps (depending on the current state of the vector) are shared by all the subclasses, which is why I figured I'd put it in a parent method. And as Lagerbaer suggests, they were already run in the equivalent of a "private" compute_a() method; what I had in mind was to call this method to bring the results to scope in the parent class, so I wouldn't have to do it in individual subclasses. Turns out, given the small number of subclasses I'm dealing with, duplicating a few lines of code may be the simplest thing to do here.

As to Nathaniel Ford's suggestions, this is why I don't find my initial solutions to be satisfactory:

  • I don't need this value once the optimisation has been run, and it changes at every optimisation step, that's why I don't think it makes sense to save it as an instance variable.
  • Returning the value would lead to the extended method behaving differently as its parent (in a signature sense).
  • I don't want anything lingering in a global scope.

As you pointed out, my thinking was not encapsulation-friendly; such a reminder is good to have occasionally!

3 Answers

There's no straightforward non-hacky way. (And I don't even know if there is a hacky way).

What you propose violates the encapsulation principle. A class hides its nitty gritty dirty internals and only exposes a neat interface with promised behavior.

Inheritance is not a mechanism to violate this principle.

In your concrete example, the issue comes from a bad interface design of T. If T had a method compute_a() that would return self.p + self.b then in your inherited class you can of course call self.compute_a().

But only do this if a is more than a mere internal implementation detail!

The short answer is 'no', in particular because the fact a sub-class has a function named the same as one in the parent class does not mean those functions share anything. When you call super().fun(), you're calling fun() on the parent class, but this is the same as calling fun() on a completely unrelated class - except insofar as the parent class is sharing it's state (instance variables).

Therefore, the only way for the sub-class instance to access computation occurring in the parent class instance's function is via normal patterns:

  • Saving the result to the instance's state (instance variables).
  • Returning the value, such that super().fun() becomes a = super().fun()
  • Saving to to some other more-global-scope value (not recommended).

When you say 'neither way is satisfactory', you should probably dig a little deeper and examine why that is. There are alternatives, such as private methods, that could resolve what seems to be a messy situation in a clean manner. That said, your example doesn't demonstrate why it is fun() is even being over-ridden, or what you hope to accomplish by sharing scope.

You can simply return a value from fun() in the parent class and then assign the returned value to a variable inside the child method. This certainly works but I don't think it's a good design:

class T:
    def __init__(self, p, b):
        self.p = p
        self.b = b
        self.fun()
        
    def fun(self):
        a = self.p + self.b
        return a
        

class TT(T):
    def __init__(self, p, b):
        super().__init__(p, b)
        
    def fun(self):
        a = super().fun()
        c = self.p * self.b * a
        return c

t = T(2, 3)
tt = TT(2, 3)
print(t.fun())   # >> 5
print(tt.fun())  # >> 30

Related