Search code examples
pythonmetaclassclass-attributes

Python - using metaclass & class attributes in object instances


I have a number of classes that inherit a common one. I need the parent class to keep track of a bunch of dependencies/relationships that are defined at the class level. Something like:

class Meta(type):
    ALLDEPENDENCIES = {}
    def __new__(meta, name, bases, attrs):
        if "DEPENDENCIES" in attrs.keys():
            for key, value in attrs.items():
                if key == "DEPENDENCIES":
                    meta.ALLDEPENDENCIES.update(attrs["DEPENDENCIES"])
        return type.__new__(meta, name, bases, attrs)

class DataTable(DataFrameWrapper, metaclass=Meta):
    pass

class Foo(DataTable):
    DEPENDENCIES = {"a":1}

class Bar(DataTable):
    DEPENDENCIES = {"b":2}

So essentially, as I create new classes (Foo, Bar, Baz...) each of them has a dictionary. I need to merge the info from each dictionary. So I'm using the metaclass, as shown above. Each class as an DEPENDENCIES attribute, and I'm gathering all of those into the ALLDEPENDENCIES attribute defined in the metaclass.

If I do this, it seems to work alright:

import Foo, Bar
print(Foo.ALLDEPENDENCIES)
>> {"a":1, "b":2}
print(Bar.ALLDEPENDENCIES)
>> {"a":1, "b":2}

However, when working if obj instances, the ALLDEPENDENCIES attributes is missing:

f = Foo()
b = Bar()
print(f.ALLDEPENDENCIES)
print(b.ALLDEPENDENCIES)

Attribute error - there is no ALLDEPENDENCIES.

I thought that the class attribute defined in the metaclass would be accessible from self.myattribute in the instances, just like DEPENDENCIES is. What am I doing wrong?


Solution

  • Meta describes how to create class but not what class that will be.

    Meta != Parent with inherited attributes

    So you have to pass proper attributes into new class:

    class Meta(type):
        _a = {}
        def __new__(meta, name, bases, attrs):
            if "d" in attrs:
                meta._a.update(attrs["d"])
            attrs["a"] = meta._a
            return type.__new__(meta, name, bases, attrs)
    
    class Data:
        pass
    
    class DataTable(Data, metaclass=Meta):
        pass
    
    class Foo(DataTable):
        d = {"a":1}
    
    class Bar(DataTable):
        d = {"b":2}
    
    f = Foo()
    print(Foo.a)
    print(f.a)
    
    {'a': 1, 'b': 2}
    {'a': 1, 'b': 2}