max
max

Reputation: 52283

Sharing base object with inheritance

I have class Base. I'd like to extend its functionality in a class Derived. I was planning to write:

class Derived(Base):
  def __init__(self, base_arg1, base_arg2, derived_arg1, derived_arg2):
    super().__init__(base_arg1, base_arg2)
    # ...
  def derived_method1(self):
    # ...

Sometimes I already have a Base instance, and I want to create a Derived instance based on it, i.e., a Derived instance that shares the Base object (doesn't re-create it from scratch). I thought I could write a static method to do that:

b = Base(arg1, arg2) # very large object, expensive to create or copy
d = Derived.from_base(b, derived_arg1, derived_arg2) # reuses existing b object

but it seems impossible. Either I'm missing a way to make this work, or (more likely) I'm missing a very big reason why it can't be allowed to work. Can someone explain which one it is?

[Of course, if I used composition rather than inheritance, this would all be easy to do. But I was hoping to avoid the delegation of all the Base methods to Derived through __getattr__.]

Upvotes: 1

Views: 311

Answers (3)

Lennart Regebro
Lennart Regebro

Reputation: 172279

To be clear here, I'll make an answer with code. pepr talks about this solution, but code is always clearer than English. In this case Base should not be subclassed, but it should be a member of Derived:

class Base(object):
    def __init__(self, base_arg1, base_arg2):
        self.base_arg1 = base_arg1
        self.base_arg2 = base_arg2

class Derived(object):
    def __init__(self, base, derived_arg1, derived_arg2):
        self.base = base
        self.derived_arg1 = derived_arg1
        self.derived_arg2 = derived_arg2

    def derived_method1(self):
        return self.base.base_arg1 * self.derived_arg1

Upvotes: 1

pepr
pepr

Reputation: 20770

The alternative approach to Alexey's answer (my +1) is to pass the base object in the base_arg1 argument and to check, whether it was misused for passing the base object (if it is the instance of the base class). The other agrument can be made technically optional (say None) and checked explicitly when decided inside the code.

The difference is that only the argument type decides what of the two possible ways of creation is to be used. This is neccessary if the creation of the object cannot be explicitly captured in the source code (e.g. some structure contains a mix of argument tuples, some of them with the initial values, some of them with the references to the existing objects. Then you would probably need pass the arguments as the keyword arguments:

d = Derived(b, derived_arg1=derived_arg1, derived_arg2=derived_arg2)

Updated: For the sharing the internal structures with the initial class, it is possible using both approaches. However, you must be aware of the fact, that if one of the objects tries to modify the shared data, the usual funny things can happen.

Upvotes: 1

Oleksii Kachaiev
Oleksii Kachaiev

Reputation: 6234

Rely on what your Base class is doing with with base_arg1, base_arg2.

class Base(object):
    def __init__(self, base_arg1, base_arg2):
        self.base_arg1 = base_arg1
        self.base_arg2 = base_arg2
        ...

class Derived(Base):
    def __init__(self, base_arg1, base_arg2, derived_arg1, derived_arg2):
        super().__init__(base_arg1, base_arg2)
        ...

    @classmethod
    def from_base(cls, b, da1, da2):
        return cls(b.base_arg1, b.base_arg2, da1, da2)

Upvotes: 2

Related Questions