Python: selective attribute overloading?

So this is what I am looking to accomplish

class Class(object):
    _map = {0: 'ClassDefault', 1:'3', 2:'xyz'}

    def handle_map(self):
        print('.'.join((self._map[0], self._map[1], self._map[2])))


class SubClass(Class):
    _map = {3: '012'}


class SubSubClass(SubClass):
    _map = {0: 'SubClassDefault', 4: 'mno'}

    def handle_map(self):
        print('.'.join((self._map[0], self.map[4])).upper())
        super().handle_map()

c, sc, ssc = Class(), SubClass(), SubSubClass()

c.handle_map()
# ClassDefault.3.xyz
sc.handle_map()
# ClassDefault.3.xyz (_map[3] is used by some other method entirely, etc.)
ssc.handle_map()
# SUBSUBCLASSDEFAULT.MNO
# 3.xyz  --OR-- SubSubClassDefault.3.xyz

      

Therefore, in essence, I want an easy way to define values ​​in parent and subclasses. I want the data to be processed by default at the level of the hierarchy that defines it, because subclasses are not supposed to do this, but I also want subclasses to be able to either override this processing altogether, or at least modify it before how it reaches the chain.

I'm not totally obsessed with _map being a dictionary. It can be a collection of objects, or even just tuples, etc. The clearest example I can give for use is generating signatures for init (). So many (but not all) parameters will be common, and I want to avoid reusing the same boiler plate. At the same time, reasonable defaults for the parent class are not sensitive to subclasses. They should be able to override these defaults.

Likewise, a subclass can have a different way in which it should interpret a given value or not, so it shouldn't completely override how it handles them if only a small modification is required.

I have half-educated ideas about how this can or should be implemented, but so far nothing has happened. I did a pretty long way to implement a metaclass to do this, but had difficulty getting parent class methods to ignore values ​​defined / handled by subclasses. When I typed this, I considered using collection.chainmap to replace a bunch of code I had written in the metaclass, but that still hasn't solved any of the problems I'm having I think.

So my question is, what is the most reasonable existing pattern for this? Alternatively, is it just not a doable thing to ask for?

+3


source to share


2 answers


Ok, I think I have a solution:

class Class:
    _map = {0: "a", 1: "b"}

    def getmap(self):
        # Create an empty map
        mymap = {}
        # Iterate through the parent classes in order of inheritance
        for Base in self.__class__.__bases__[::-1]:
            # Check if the class has a "getmap" attribute
            if issubclass(Base, Class):
                # Initialize the parent class
                b = Base()
                # If so, add its data to mymap
                mymap.update(b.getmap())
        # Finally add the classes map data to mymap, as it is at the top of the inheritance
        mymap.update(self._map)
        return mymap

class SubClass(Class):
    _map = {2: "c"}

class SubSubClass(SubClass):
    _map = {0: "z", 3: "d"}

c = Class()
sc = SubClass()
ssc = SubSubClass()

print(c.getmap())  # -> {0: 'a', 1: 'b'}
print(sc.getmap())  # -> {0: 'a', 1: 'b', 2: 'c'}
print(ssc.getmap())  # -> {0: 'z', 1: 'b', 2: 'c', 3: 'd'}

      



As far as I can tell, this does everything you need. Let me know if you have any questions.

+1


source


Key question: What happens when _map

changes in the parent class?

  • Subclasses
  • You should see the change β†’ , using something in accordance with the answers @noahbkim

    Subclasses
  • You do not need to see a change β†’ , using a decorator class or metaclass cementing created class.



The metaclass solution looks like this:

class ClassMeta(type):
    def __new__(metacls, cls, bases, classdict):
        new_class = super(ClassMeta, metacls).__new__(metacls, cls, bases, classdict)
        _map = {}
        prev_classes = []
        obj = new_class
        for base in obj.__mro__:
            if isinstance(base, ClassMeta):
                prev_classes.append(base)
        for prev_class in reversed(prev_classes):
            prev_class_map = getattr(prev_class, '_map', {})
            _map.update(prev_class_map)
        new_class._map = _map
        return new_class

class Class(object, metaclass=ClassMeta):
    ...

      

+1


source







All Articles