1

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__.]

max
  • 49,282
  • 56
  • 208
  • 355
  • "a Derived instance that shares the Base object (doesn't re-create it from scratch)." - That makes no sense. There is no Base instance when you instantiate the Derived class. There is only a Derived object, so there is nothing to share or re-create. I think you shouldn't subclass Base at all. – Lennart Regebro Dec 10 '12 at 12:12
  • Also, since the Base and Derived has different parameters to `__init__` you should in general avoid `super()`. – Lennart Regebro Dec 10 '12 at 12:13
  • @LennartRegebro: regarding your 1st comment, the `Base` instance I'm trying to share is `b`. It was created before I was trying to create a `Derived` instance. – max Dec 10 '12 at 20:45
  • @LennartRegebro: regarding your 2nd comment, if I have inheritance, isn't it very common that a derived class extends the functionality of the base class in such a way that it needs extra parameters to `__init__`? If I don't use `super()`, there's no way to do inheritance at all, is there? (Well, unless the base `__init__` just happens to work fine in the derived class, without any modification.) – max Dec 10 '12 at 20:47
  • Since you are trying to share an instance with one or several other instances, subclassing the class of the first instance is not a solution. It does in fact not help you at all. Subclassing is about sharing code between classes, not data between instances. – Lennart Regebro Dec 11 '12 at 00:05
  • `super()` is not necessary for inheritance in any way. It's only useful when you are doing multiple inheritance in libraries, which is something that is probably best avoided anyway. But that's a different question. To which the answer is here: http://stackoverflow.com/a/5067095/126214 – Lennart Regebro Dec 11 '12 at 00:11
  • @LennartRegebro just on the `super()` subject specifically, do you mean I should have called `Base.__init__` instead of `super().__init__`? If so, I agree 100%, I just used `super()` by habit. – max Dec 11 '12 at 03:37
  • Exactly. Never get the habit to use `super()`. It solves a very specific and unusual problem. – Lennart Regebro Dec 11 '12 at 06:04

3 Answers3

2

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)
Alexey Kachayev
  • 6,106
  • 27
  • 24
  • That would work but only if `__init__` was a cheap method that simply assigns its arguments to the instance. Unfortunately, it's far from that. This is the main reason `Base` instances are expensive to create. – max Dec 10 '12 at 11:16
  • @max I understood that `Base` is expensive, but what's problem with adding 2 lines of code to `__init__` in order to store 2 additional attributes? – Alexey Kachayev Dec 10 '12 at 11:22
  • `base_arg1` is a filename, `base_arg2` is the format description, `Base.__init__` parses the (usually huge) file and stores the result in an internal data structure. – max Dec 10 '12 at 11:24
  • @max in this case you don't need `super`. possible, you even don't need inheritance (you "has a" instead of "is a"). – Alexey Kachayev Dec 10 '12 at 11:35
  • Not only do you not need super, you shouldn't use it as the two methods have different parameters. I'd also rather go for peprs solution and store b as an attribute. – Lennart Regebro Dec 10 '12 at 12:15
1

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.

pepr
  • 20,112
  • 15
  • 76
  • 139
1

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
Lennart Regebro
  • 167,292
  • 41
  • 224
  • 251