2

I have to following module:

class A():
    a = 1
class B(A):
    b = 2

In another file I want to replace class A (like here):

import module
class C():
    c = 3
module.A = C

When creating a B object, it should inherit now from C, and have a .c attribute, however, it does not:

assert module.B().c == 3

However, this raises: AttributeError: 'B' object has no attribute 'c'

How can this be fixed?

Vasco
  • 1,177
  • 11
  • 28
  • 2
    When `B` was defined, `A` was still the original `A`. `B` inherits from the class object that `A` happened to point to at the time. You can change `A`, but by then `B` is already referencing the object behind `A` not the current `A`. – tdelaney Apr 12 '18 at 16:18
  • Is there a solution that not requires recreating the whole module? – Vasco Apr 12 '18 at 16:20
  • It would depend on what your goal is. You could just add or overwrite things on the original `A`. In your example, `module.A.c = 3`. You can do the same thing with the methods on `A`. Python looks these up dyamically, so they can be changed mid stream. – tdelaney Apr 12 '18 at 16:24
  • But if you are adding new things to A that existing inheritors of A don't know or care about, you may be interested in [mixins](https://stackoverflow.com/questions/533631/what-is-a-mixin-and-why-are-they-useful). – tdelaney Apr 12 '18 at 16:27

2 Answers2

2

Variables are references to objects. When you reassign a variable, it doesn't affect the previous object unless its reference count goes to zero and it is deleted. In your case

class A:
    a = 1
class B(A):
    b = 2

B gets the class object that happened to be assigned to A at that moment and uses that as the parent. As long as B uses super for any future reference to its parent, B never touches the A variable again. And your reassignment of A makes no difference.

But you did say Monkey Patch, and that is still doable. Just put stuff onto the original A class and all inheritors will see it:

import module
module.A.c = 3
assert module.B().c == 3

You can also add or replace methods

import module

def d(self):
    return 4

module.A.d = d
assert module.B().d() == 4

Alternately, if you just want this change to happen for some inheritors of A, consider mixins What is a mixin, and why are they useful?

tdelaney
  • 73,364
  • 6
  • 83
  • 116
  • This means that one has to add every method and attribute from C separately to A. – Vasco Apr 12 '18 at 16:50
  • @Vasco - your edited change to bulk add items from one class `__dict__` to another is interesting but further than I want to go. In that case, multiple inheritance and the mixin strategy would be more my cup of tea. It would be a good alternate answer to this question, though. – tdelaney Apr 12 '18 at 17:57
1

The ideas in the post of tdelaney can be automatized:

def replace_class(oldcls, newcls):
    '''Adds/replaces the contents for oldcls with newcls, leaving 
    references to oldcls in-place. Usefull for monkey patching'''
    for key, val in newcls.__dict__.items():
        if key != '__dict__':
            setattr(oldcls, key, val)

Now this works

replace_class(module.A, C)
module.B().c == 2
Vasco
  • 1,177
  • 11
  • 28