0

My intention is to get a list completed with the values that are assigned to a variable in each class of superior nature in the simplest way possible.

class First:
    list = []
    def get_final_list(self):
        return self.list

class Second(First):
    list = ['one']

class Third(Second):
    list = ['two']

Result: list = ['one','two']

This is an example of the final result but obviously the whole logic of the function to return this value is missing.

Vy Do
  • 46,709
  • 59
  • 215
  • 313
paralosreg
  • 141
  • 1
  • 15
  • For classes that deal with lists, using a name like `list` and shadowing the builtin isn't the best thing to do. – cs95 Jan 17 '18 at 12:39
  • 1
    What would trigger the addition of new element? In your code you are just overriding the variable each time. – Chen A. Jan 17 '18 at 12:40

2 Answers2

2

I did something similar recently where I wanted child classes to be able to define additional values within attributes of their parents. You can do this using metaclasses, which allow you to hook into class creation in the same way that classes let you hook into instance creation.

In your case, for example, you could do something like:

class ListCombiner(type):
    def __new__(cls, name, bases, dct):
        l = []
        for base in bases:
            l = getattr(base, 'list', []) + []
        dct['list'] = l + dct.get('list', [])
        return type.__new__(cls, name, bases, dct)

class First(metaclass=ListCombiner):
    list = []
    def get_final_list(self):
        return self.list

class Second(First):
    list = ['one']

class Third(Second):
    list = ['two']

Now the result is:

>>> Third().list
['one', 'two']

For more information on metaclasses, see e.g. What are metaclasses in Python?

If you're really interested, you can see where I introduced it in this commit; this shows how I was able to replace some awkward code with the metaclass. The fact that I was using sets made it slightly easier, as I didn't care about the order.

jonrsharpe
  • 115,751
  • 26
  • 228
  • 437
  • An interesting way, I must prove how it interferes with the operation of the rest of the class, but it perfectly fulfills what I was looking for. – paralosreg Jan 17 '18 at 13:18
  • @paralosreg that's the only way it changes the behaviour. But if you have tests you can check for any regressions. – jonrsharpe Jan 17 '18 at 13:24
  • I tested it on a Django project and it works correctly as long as the inheritance order is Third (OtherClass, Second). In case of being Third (Second, Otherclass) ignores the metaclass and the result is that of the local variable of the class. Is there a way to solve this so that it works in the second way? – paralosreg Jan 18 '18 at 12:03
  • @paralosreg if you're doing this within a Django app you should include that in your [mcve]; Django is already using metaclasses that may require changes to the approach. – jonrsharpe Jan 18 '18 at 12:11
1

How about using proper methods etc. instead of a mere field in the class?

class First:
    def list(self):
        return []
    def get_final_list(self):
        return self.list

class Second(First):
    def list(self):
        return super().list() + ['one']

class Third(Second):
    def list(self):
        return super().list() +  ['two']

Then you can:

Third().list() # returns ['one', 'two']
Alfe
  • 56,346
  • 20
  • 107
  • 159