5

First - please accept my apologies if this is a duplicate - I have the feeling that I have seen some sort of similar discussion before, but I really cannot find it.

My question regards object composition in Python that should look like inheritance from within each minor of the composite classes. The use case is that multiple object instances share a common core of attributes and their values (and not only a common structure, which would be a classic inheritance case instead).

I could do this with a simple attribute, i.e. by simply having each Class having one attribute called "shared_attributes", which is in itself a class storing all the values:

class CoreClass(object):
    def __init__(self):
        self.attr = 'asdf'


class CompClass1(object):
    def __init__(self, core):
        self.core_attr = core


class CompClass2(object):
    def __init__(self, core):
        self.core_attr = core

But this requires me to access each shared attribute through the class.core_attr attribute, which I do not want (for several reasons, one of which is that this would require an extensive rewrite of large sections of code).

So, instead I would like to use a composite pattern relying on Python's built-in __getattr__ object method, as such:

class TestClass1(object):
    def __init__(self):
        self.attr1 = 1

    def func_a(self):
        return 'a'


class CompClassBase(object):
    def __init__(self, test_class):
        self.comp_obj = test_class

    def __getattr__(self, item):
        return getattr(self.comp_obj, item)


class CompClass1(CompClassBase):
    def __init__(self, test_class):
        CompClassBase.__init__(self, test_class)
        self.attr2 = 13

    def func_b(self):
        return '1b'


class CompClass2(CompClassBase):
    def __init__(self, test_class):
        CompClassBase.__init__(self, test_class)
        self.attr2 = 23

    def func_b(self):
        return '2b'


if __name__ == '__main__':
    tc = TestClass1()
    cc1 = CompClass1(test_class=tc)
    cc2 = CompClass2(test_class=tc)
    print cc1.attr1
    print cc1.attr2
    print cc1.func_a()
    print cc1.func_b()
    print cc2.attr1
    print cc2.attr2
    print cc2.func_a()
    print cc2.func_b()

Which prints, as it should, the following:

1
13
a
1b
1
23
a
2b

This pattern fits my needs perfectly, but there is something about it that wants to make me be certain about it ...

EDIT: (to respond to some comments) It is essential that this pattern will share all attributes in the shared class (given the previous objects):

cc1.attr1 = 'this is a test'
cc2.attr1  # must now be 'this is a test' as well!

2nd EDIT: I have used this pattern now for several weeks, and it works beautifully. However, I'm still hoping for some discussion, since I want to include this pattern in my standard toolkit from now on :-)

So now my question to you is simple: Is this a good practice? Does this particular Python pattern have any disadvantages? Should I be aware of some dangers here?

cleros
  • 4,005
  • 1
  • 20
  • 30
  • 1
    Seems like using a mix-in class would be much simpler. – chepner Sep 09 '16 at 14:16
  • Why not just use inheritance? Also, is there a reason for `self.comp_obj.__getattribute__(item)` rather than `getattr(self.comp_obj, item)`? – mgilson Sep 09 '16 at 14:24
  • Oh, I see. Updates to the shared instance of `TestClass1` should be visible to any instance using it. – chepner Sep 09 '16 at 14:25
  • @mgilson good point, that :-) I updated the question to use getattr. Thanks! And chepner 's comment answers the problem with inheritance. ( I might even want to make the shared instance a singleton ...) – cleros Sep 09 '16 at 14:57
  • @mgilson: most of the time composition is better than inheritance. – mguijarr Sep 09 '16 at 14:59
  • `__getattr__` is more about delegation than composition – mguijarr Sep 09 '16 at 15:00
  • @mguijarr -- I think that asserting that one way is better than the other is really just going to start a flame war :-). They're different paradigms with different strengths and weaknesses and for any given piece of code those strengths and weaknesses should be evaluated and the best paradigm chosen. _Here_, since OP doesn't want to have to refer to the delegated attribute, inheritance starts to look more attractive (to me at least), but without knowing more about the exact problem it's hard to say... – mgilson Sep 09 '16 at 15:37
  • @mgilson I have updated the question to explain why inheritance does not work – cleros Sep 09 '16 at 17:29
  • @cleros -- I'm still not certain that I understand the problem with inheritance. Is the problem that you don't want to construct a _new_ object from the old one? Or that the type of item that you want to compose with isn't aways the same? – mgilson Sep 09 '16 at 17:31
  • @mgilson -- sorry if my edit does not make this sufficiently clear: yes, I do _not_ want to create a new object, since I need the exact object references as determined by the shared object. – cleros Sep 09 '16 at 17:32

0 Answers0