-1

So, I have a metaclass that caches instances of objects it's classes create, to avoid duplicating instances:

class _CachingMeta(type):
    __cache__ = {}

    def __init__(self, n, b, d):
        # ... Do stuff...
        return super(_CachingMeta, self).__init__(n, b, d)

    def __call__(cls, *a, **kw):
        # Simplified caching key
        key = frozenset((cls, frozenset(a), frozenset(kw.items())))
        if key not in _CachingMeta.__cache__:
            _CachingMeta.__cache__[key] = super(_CachingMeta, cls).__call__(*a, **kw)
        return _CachingMeta.__cache__[key]

class StaticClass(object):
    __metaclass__ = _CachingMeta

    def __init__(self, *a, **kw):
        pass

inst1 = StaticClass('arg1')
inst2 = StaticClass('arg1')
inst3 = StaticClass('arg3')

print (inst1)
print (inst2)
print (inst3)

prints:

<__main__.StaticClass object at 0x7f7ad8690c90>
<__main__.StaticClass object at 0x7f7ad8690c90>
<__main__.StaticClass object at 0x7f7ad8690d10>

I would like to also dynamically create classes and I would like the classes and the instances of those classes to be cached, and I thought I could create a metaclass that both extends _CachingMeta and uses it as a __metaclass__, but this seems to fail, and I can't wrap my head around why, e.g.:

class _ClassCachingMeta(_CachingMeta):
    '''
    Purpose: to cache generated classes and their instances
    '''
    __metaclass__ = _CachingMeta

def create_class(param):
    class DynamicClass(object):
        __metaclass__ = _ClassCachingMeta # A meta class that caches both  the classes it creates and their instances
        __param__ = param
    return DynamicClass

prints:

Traceback (most recent call last):
  File "../test2.py", line 29, in <module>
    class _ClassCachingMeta(_CachingMeta):
  File "../test2.py", line 7, in __init__
    return super(_CachingMeta, self).__init__(n, b, d)
TypeError: Error when calling the metaclass bases
    descriptor '__init__' requires a 'type' object but received a 'str'

It seems like _ClassCachingMeta doesn't get a bound __init__ method before the it's __metaclass__.__init__ is called (which I guess are the same method/function?) (e.g.):

class _CachingMeta(type):
    __cache__ = {}

    def __init__(self, n, b, d):
        print ("initilizing {0}".format(self))
        print (super(_CachingMeta, self).__init__)
        return super(_CachingMeta, self).__init__(n, b, d)

    def __call__(cls, *a, **kw):
        # Simplified caching key
        key = frozenset((cls, frozenset(a), frozenset(kw.items())))
        if key not in _CachingMeta.__cache__:
            _CachingMeta.__cache__[key] = super(_CachingMeta, cls).__call__(*a, **kw)
        return _CachingMeta.__cache__[key]

class StaticClass(object):
    __metaclass__ = _CachingMeta

    def __init__(self, *a, **kw):
        pass

class _ClassCachingMeta(_CachingMeta):
    '''
    Purpose: to cache generated classes and their instances
    '''
    __metaclass__ = _CachingMeta

def create_class(param):
    class DynamicClass(object):
        __metaclass__ = _ClassCachingMeta # Cache the class and it's instances
        __param__ = param
    return DynamicClass

gives:

initilizing <class '__main__.StaticClass'>
<method-wrapper '__init__' of _CachingMeta object at 0xc44cf0>
initilizing <class '__main__._ClassCachingMeta'>
<slot wrapper '__init__' of 'type' objects>
Traceback (most recent call last):
  File "../test2.py", line 32, in <module>
    class _ClassCachingMeta(_CachingMeta):
  File "../test2.py", line 9, in __init__
    return super(_CachingMeta, self).__init__(n, b, d)
TypeError: Error when calling the metaclass bases
    descriptor '__init__' requires a 'type' object but received a 'str'

Is there a straightforward way to implement this, or do I need to go with a different approach? Also, if anyone can help me understand why a class can't extend it's own __metaclass__ that would be awesome.

ipetrik
  • 1,749
  • 18
  • 28
  • duplicate https://stackoverflow.com/questions/100003/what-are-metaclasses-in-python?rq=1 – Morse Apr 24 '18 at 20:26

1 Answers1

2

If you try to use a metaclass as the metaclass of its own subclasses, then subclasses are both instances and subclasses of the first class.

super(thing, other) can mean one of two things, depending on whether other is an instance of thing or a subclass. When other is both an instance and a subclass, super gets very confused.

There isn't really a good way to resolve the ambiguity for super. It's best to avoid this kind of situation.

user2357112
  • 260,549
  • 28
  • 431
  • 505