demberto
demberto

Reputation: 540

Am I using metaclass correctly?

I just realised that __setattr__ doesn't work on the class itself. So this implementation,

class Integer:
    me_is_int = 0

    def __setattr__(self, name, value):
        if not isinstance(value, int):
            raise TypeError

doesn't raise on this:

Integer.me_is_int = "lol"

So, switching to a metaclass:

class IntegerMeta:
    def __setattr__(cls, name, value):
        if not isinstance(value, int):
            raise TypeError

class Integer(metaclass=IntegerMeta):
    me_is_int = 0

this works, but this:

Integer().me_is_int = "lol"

doesn't work yet again. So do I need to copy the __setattr__ method in Integer again to make it work on instances? Is it not possible for Integer to use IntegerMeta's __setattr__ for instances?

Upvotes: 0

Views: 123

Answers (1)

jsbueno
jsbueno

Reputation: 110271

You are right in your reasoning: having a custom __setattr__ special method in the metaclass will affect any value setting on the class, and having the it on the class will affect all instances of the class.

With that in mind, if you don't want to duplicate code, is to arrange the metaclass itself to inject the logic in a class, whenever it is created.

The way you've written it, even thinking as an example, is dangerous, as it will affect any attribute set on the class or instances - but if you have a list of the attributes you want to guard in that way, it would also work.



attributes_to_guard = {"me_is_int",}

class Meta:
    def __init__(cls, name, bases, ns, **kw):
        # This line itself would not work if the setattr would not check
        # for a restricted set of attributes to guard:
        cls.__setattr__ = cls.__class__.__setattr__
        # Also, note that this overrides any manually customized
        # __setattr__ on the classes. The mechanism to call those, 
        # and still add the guarding logic in the metaclass would be
        # more complicated, but it can be done
        super().__init__(name, bases, ns, **kw)

    def __setattr__(self, name, value):
        if name in attributes_to_guard not isinstance(value, int):
            raise TypeError()

class Integer(metaclass=Meta):
    me_is_int = 0

Upvotes: 1

Related Questions