Skip to content
Advertisement

Why is my class factory not populating my dynamic object’s `__dict__` attribute

I have a class factory which dynamically builds new types (not objects/instances). The code is currently working per-se, but I can’t figure out where the attributes I give it are stored. Below is an image of my driver code and output:

Driver code for dynamic class creation

All attributes and methods are set and working as expected, but they’re not registered in the __dict__ property. This is not ok, since a downstream use case will search __dict__ for those custom types. The entire “working” factory is here:

from typing import Iterable, Any

from types_extensions import void


class DynamicClassFactory:

    def __init__(self, default_base_classes: Iterable[type] = None, prefix: str = '', suffix: str = '',
                 attributes: dict[str, Any] = None) -> void:
        self.prefix: str = prefix
        self.suffix: str = suffix
        self.mixins: list[type] = [x for x in default_base_classes or (object,)]
        self.attributes: dict[str, Any] = attributes or {}

    def build(self, class_name: str, extra_mixins: Iterable[type] = (),
              attributes: dict[str, Any] = None) -> type:

        return type(
            self.prefix + class_name + self.suffix,
            self._create_bases(extra_base_classes=extra_mixins),
            {**self.attributes, **(attributes or {})}
        )

    def _create_bases(self, extra_base_classes: Iterable[type]) -> tuple[type]:
        return tuple(self.mixins + [x for x in extra_base_classes])

I have looked online and mainly used this as a guide: https://realpython.com/python-metaclasses/ (for this particular use case of type(), Ctrl+F in the page and search “You can also call type() with three arguments” without the quotes.

Thanks in advance!

PS: I know I don’t have correct error handling for illegal characters in names, it’s planned.

Advertisement

Answer

When you pass attributes to the type class, you’re actually passing class attributes instead of instance attributes. From documentation:

The dict dictionary contains attribute and method definitions for the class body; it may be copied or wrapped before becoming the __dict__ attribute. The following two statements create identical type objects:

class X:
   a = 1

X = type('X', (), dict(a=1))

So the following holds true:

my_class2 = factory.build(...)
print(my_class2.__dict__)    # Outputs {..., x: 1, y: 2}

obj2 = my_class2()
print(obj2.__dict__)         # Outputs {}

You may ask how can you access obj2.y successfully. That’s because when Python does not find a attribute in the instance (in this case, obj2), it’ll look up in the class attributes (in this case, my_class2). Since there is one, it’ll evaluate to it.

User contributions licensed under: CC BY-SA
8 People found this is helpful
Advertisement