Bentley Carr
Bentley Carr

Reputation: 702

Override attribute get behaviour but only by external methods in Python

Consider the following code:

class A():
    def __init__(self, thing):
        self.thing = thing
        
    def do_something(self):
        if self.thing > 0:
            print('thing is positive')
        else:
            print('thing is not positive')
            
def some_function(a):
    if a.thing > 0:
        print('this thing is positive')
    else:
        print('this thing is not positive')
        
class B(A):
    @property
    def thing(self):
        return 0
    
    @thing.setter
    def thing(self, val):
        pass

    # Purposely don't want to override A.do_something
    
a = A(5)
print(a.thing)   # 5
a.do_something() # thing is positive
some_function(a) # this thing is positive
isinstance(a, A) # True

b = B(5)
print(b.thing)   # 0
b.do_something() # thing is not positive  (!!! - not what we want - see below)
some_function(b) # this thing is not positive
isinstance(b, A) # True

Suppose that do_something is a complicated function which we don't want to override. This could be because it is in an external package and we want to be able to keep using the latest version of this package containing A without having to update B each time. Now suppose that an outside function accesses a.thing by referencing it directly. We want B to extend A so that this external function always sees b.thing == 0. However, we want to do this without modifying the behaviour of internal methods. In the example above, we want to modify the behaviour of some_function, but we do this at the cost of also changing the behaviour of the internal method b.do_something.

The obvious way to fix this would be to have the external functions some_function use a get_thing() method. But if these external functions have been already written in another package modifying these is not possible.

Another way would be to have B update the value of self.thing before calling the parent class' method,

class B(A):
    def __init__(self, thing):
        self.thing = 0
        self._thing = thing
    
    def do_something(self):
        self.thing = self._thing
        rval = super().do_something()
        self.thing = 0
        return rval

however this seems clunky and if the developer of A adds new methods, then B would change the behaviour of these methods if it is not updated.

Is there a best practice on how to go about extending a class like this which allows use to override __getattribute__ if called by an external function, but without changing any internal behaviour?

Upvotes: 2

Views: 61

Answers (1)

Gorisanson
Gorisanson

Reputation: 2312

I think you can set class B like the following to achieve what you want:

class B:
    def __init__(self, thing):
        self.thing = 0
        self._a = A(thing)

    def __getattr__(self, name):
        return getattr(self._a, name)

The full code is below:

class A:
    def __init__(self, thing):
        self.thing = thing

    def do_something(self):
        if self.thing > 0:
            print('thing is positive')
        else:
            print('thing is not positive')


def some_function(a):
    if a.thing > 0:
        print('this thing is positive')
    else:
        print('this thing is not positive')


class B:
    def __init__(self, thing):
        self.thing = 0
        self._a = A(thing)

    def __getattr__(self, name):
        return getattr(self._a, name)


if __name__ == '__main__':
    a = A(5)
    print(a.thing)    # 5
    a.do_something()  # thing is positive
    some_function(a)  # this thing is positive

    b = B(5)
    print(b.thing)    # 0
    b.do_something()  # thing is positive
    some_function(b)  # this thing is not positive

Upvotes: 1

Related Questions