1

Simple example Parent is class with version attribute. I want to inherit from this class but avoid sharing versions attribute among all inherited classes.

class Parent(object):
    versions = []

    @classmethod
    def add_version(cls, version):
        cls.versions.append(version)

class P1(Parent):
    pass

class P2(Parent):
    pass

class Thing(object):
    pass

P1.add_version(Thing)
# should be Thing
print(P1.versions)
# should be empty [] by my design but it works other way in Python
# how to achieve to different versions fields without coding it in child classes?
print(P2.versions)

Produce such result since version is shared between classes - how to avoid?

[<class '__main__.Thing'>]
[<class '__main__.Thing'>]
Chameleon
  • 9,722
  • 16
  • 65
  • 127

3 Answers3

2

Technically you dont need a metaclass - a simple class decorator also works (well, kind of...):

def versions_container(cls):
    if "versions" not in cls.__dict__:
        print("adding versions to class {}".format(cls.__name__))
        cls.versions = []
    return cls


@versions_container # this one is useless but anyway...
class Parent(object):
    versions = []
    @classmethod
    def add_version(cls, version):
        cls.versions.append(version)

@versions_container
class P1(Parent):
    pass

@versions_container
class P2(Parent):
    pass

class Thing(object):
    pass

P1.add_version(Thing)
print(P1.versions)
print(P2.versions)

but this is barely better than manually adding the versions attribute in each child class.

The custom metaclass solution is not such a big deal and will make sure users of your code won't break the expectations:

class VersionsContainer(type):
    def __new__(meta, name, bases, attrs):
        if "versions" not in attrs:
            attrs["versions"] = []
        return type.__new__(meta, name, bases, attrs)

class Parent(object, metaclass=VersionContainer):
    @classmethod
    def add_version(cls, version):
        cls.versions.append(version)

class P1(Parent):
    pass

class P2(Parent):
    pass

class Thing(object):
    pass

P1.add_version(Thing)
print(P1.versions)
print(P2.versions)
bruno desthuilliers
  • 75,974
  • 6
  • 88
  • 118
1

You will need to redeclare versions in each child class:

class P1(Parent):
    versions = []

class P2(Parent):
    versions = []

The only way to avoid doing this would be to use metaclasses, which is far more hassle than you need.

Daniel Roseman
  • 588,541
  • 66
  • 880
  • 895
  • Good idea but maybe there more simpler solution but you are right metaclass is need. – Chameleon Oct 28 '16 at 10:14
  • " which is far more hassle than you need" => well, depends on the context. If that's for a couple child classes living in the same module and none of these classes are expected to be extended by someone else then yes indeed, just manually adding the `versions` attribute is the obvious solution. Now if it's library or framework code and `Parent` is supposed to be extended by the library or framework user then the five lines metaclass is really worth the "hassle" ;) – bruno desthuilliers Oct 28 '16 at 10:37
1

Instead of storing the versions as class attributes of each class, you can store them all in one dict.

class Parent(object):

    _versions = {}  # class -> version_list

    @classproperty
    def versions(cls):
        return cls._versions.setdefault(cls, [])

    @classmethod
    def add_version(cls, version):
        cls.versions.append(version)

class P1(Parent):
    pass
class P2(Parent):
    pass

P1.add_version(1)
print(P1.versions)
print(P2.versions)

(classproperty is a very useful non-builtin construct. You can find it in this answer)

I'm not neccessarily saying this is the best or most elegant approach, but it is really simple and avoids metaclasses, etc. The clear disadvantage of this approach is that it requires a class to store data for its subclasses, which is not great design-wise.

Community
  • 1
  • 1
shx2
  • 61,779
  • 13
  • 130
  • 153