50

Lets say I have a library function that I cannot change that produces an object of class A, and I have created a class B that inherits from A.

What is the most straightforward way of using the library function to produce an object of class B?

edit- I was asked in a comment for more detail, so here goes:

PyTables is a package that handles hierarchical datasets in python. The bit I use most is its ability to manage data that is partially on disk. It provides an 'Array' type which only comes with extended slicing, but I need to select arbitrary rows. Numpy offers this capability - you can select by providing a boolean array of the same length as the array you are selecting from. Therefore, I wanted to subclass Array to add this new functionality.

In a more abstract sense this is a problem I have considered before. The usual solution is as has already been suggested- Have a constructor for B that takes an A and additional arguments, and then pulls out the relevant bits of A to insert into B. As it seemed like a fairly basic problem, I asked to question to see if there were any standard solutions I wasn't aware of.

saffsd
  • 23,742
  • 18
  • 63
  • 67

5 Answers5

39

This can be done if the initializer of the subclass can handle it, or you write an explicit upgrader. Here is an example:

class A(object):
    def __init__(self):
        self.x = 1

class B(A):
    def __init__(self):
        super(B, self).__init__()
        self._init_B()
    def _init_B(self):
        self.x += 1

a = A()
b = a
b.__class__ = B
b._init_B()

assert b.x == 2
ironfroggy
  • 7,991
  • 7
  • 33
  • 44
  • I must say that this is the greatest hacky solution. +1 | Googlers must also check: https://stackoverflow.com/questions/49718703/typeerror-class-assignment-only-supported-for-heap-types-or-moduletype-subc – Eray Erdin Oct 27 '18 at 07:46
  • Part of the difficulty in the question is that I don't necessarily have access to a constructor of class A but maybe just a class method that returns an A. In which case I don't think this solution works. – guibar Feb 04 '21 at 10:14
25

Since the library function returns an A, you can't make it return a B without changing it.

One thing you can do is write a function to take the fields of the A instance and copy them over into a new B instance:

class A: # defined by the library
    def __init__(self, field):
        self.field = field

class B(A): # your fancy new class
    def __init__(self, field, field2):
        self.field = field
        self.field2 = field2 # B has some fancy extra stuff

def b_from_a(a_instance, field2):
    """Given an instance of A, return a new instance of B."""
    return B(a_instance.field, field2)


a = A("spam") # this could be your A instance from the library
b = b_from_a(a, "ham") # make a new B which has the data from a

print b.field, b.field2 # prints "spam ham"

Edit: depending on your situation, composition instead of inheritance could be a good bet; that is your B class could just contain an instance of A instead of inheriting:

class B2: # doesn't have to inherit from A
    def __init__(self, a, field2):
        self._a = a # using composition instead
        self.field2 = field2

    @property
    def field(self): # pass accesses to a
        return self._a.field
    # could provide setter, deleter, etc

a = A("spam")
b = B2(a, "ham")

print b.field, b.field2 # prints "spam ham"
Kiv
  • 31,940
  • 6
  • 44
  • 59
  • 6
    Python objects know their class by setting the `.__class__` attribute. You can actually set it to something else: http://stackoverflow.com/a/29256784/1423333 – Jörn Hees Mar 25 '15 at 13:52
  • I'm upvoting using a @property to pull the data from self._a. Composition lends itself to writing pure methods which are vastly easier to unittest. Inheritance is NOT wrong and it really depends on your use case. But my experience says composition tends to be easier to deal with long term. – srock Apr 13 '15 at 15:03
  • It seems to me you have no guarantee that your B object will behave in the same way as the A object it was created from unless you have made absolutely sure you have copied the entire guts of object A into B. At which point you are not really doing inheritance anymore. – guibar Feb 04 '21 at 10:11
20

you can actually change the .__class__ attribute of the object if you know what you're doing:

In [1]: class A(object):
   ...:     def foo(self):
   ...:         return "foo"
   ...:

In [2]: class B(object):
   ...:     def foo(self):
   ...:         return "bar"
   ...:

In [3]: a = A()

In [4]: a.foo()
Out[4]: 'foo'

In [5]: a.__class__
Out[5]: __main__.A

In [6]: a.__class__ = B

In [7]: a.foo()
Out[7]: 'bar'
Jörn Hees
  • 3,338
  • 22
  • 44
  • Shouldn't class B inherit from A to better match the original question? Also, it might be wise to call `super().foo()` if you're overriding a method (unless you mean to rip out the original functionality of course :-) ) – hugovdberg Aug 05 '20 at 20:29
2

Monkeypatch the library?

For example,

import other_library
other_library.function_or_class_to_replace = new_function

Poof, it returns whatever you want it to return.

Monkeypatch A.new to return an instance of B?

After you call obj = A(), change the result so obj.class = B?

Andrew Dalke
  • 14,889
  • 4
  • 39
  • 54
1

Depending on use case, you can now hack a dataclass to arguably make the composition solution a little cleaner:

from dataclasses import dataclass, fields

@dataclass
class B:
    field: int  # Only adds 1 line per field instead of a whole @property method

    @classmethod
    def from_A(cls, a):
        return cls(**{
            f.name: getattr(a, f.name)
            for f in fields(A)
        })
Alecg_O
  • 892
  • 1
  • 9
  • 19