2

I would like to "stack" metaclasses.

def dec(cls):
    class newmeta(cls.__metaclass__):
        def __call__(cls, *args, **kwargs):
            obj = cls.__metaclass__.__call__(cls, *args, **kwargs)
            obj.x *= 2 #this happens AFTER __init__ is called
    cls.__metaclass__ = newmeta
    return cls

@dec
@dec
class Foo(object):
    def __init__(self, x):
        self.x = 5

print Foo().x #expect 20

However, I don't seem to be able to access Foo.__metaclass__ in this fashion.... in fact ("__metaclass__" in Foo.__dict__) == False

How would one go about this?

georgexsh
  • 15,984
  • 2
  • 37
  • 62
Him
  • 5,257
  • 3
  • 26
  • 83
  • What are you actually trying to *achieve* with this? Have you read e.g. https://stackoverflow.com/q/5120688/3001761? – jonrsharpe Dec 23 '17 at 15:46
  • The goal is to perform a series of events after the `__init__` method is called. I might just solve it with mixins, and call a bunch of supers at the end. – Him Dec 23 '17 at 15:57
  • "Metaclasses are deeper magic than 99% of users should ever worry about." If there is a way to accomplish what you want without using them, do it. – chepner Dec 24 '17 at 16:06

2 Answers2

3

Alter existsing cls.__metaclass__ has no effect, as metaclass determine how class built, you have to create a new class object with your metaclass:

def dec(cls):
    basetype = type(cls)
    class newmeta(basetype):
        def __call__(cls, *args, **kwargs):
            obj = super(newmeta, cls).__call__(*args, **kwargs)
            obj.x *= 2
            return obj
    new_cls = newmeta(cls.__name__, cls.__bases__, dict(cls.__dict__))
    return new_cls


@dec
@dec
class Bar(object):

    def __init__(self):
        self.x = 5


o = Bar()
print o.x

yields:

20

ps: this method works for both python 2 and 3.

georgexsh
  • 15,984
  • 2
  • 37
  • 62
0

If you are talking about Python 3.5+, __metaclass__ is no longer supported, as desribed here __metaclass__ in Python3.5

Omni
  • 1,002
  • 6
  • 12