Kumba
Kumba

Reputation: 2428

Metaclasses and __slots__?

So, I am reading a bit about metaclasses in Python, and how type()'s three-argument alter-ego is used to dynamically create classes. However, the third argument is usually a dict that initializes the to-be created class' __dict__ variable.

If I want to dynamically create classes based on a metaclass that uses __slots__ instead of __dict__, how might I do this? Is type() still used in some fashion along with overriding __new__()?

As an FYI, I am aware of the proper uses for __slots__, to save memory when creating large numbers of a class versus abusing it to enforce a form of type-safety.


Example of a normal (new-style) class that sets __metaclass__ and uses a __dict__:

class Meta(type):
    def __new__(cls, name, bases, dctn):
        # Do something unique ...
        return type.__new__(cls, name, bases, dctn)

class Foo(object):
    __metaclass__ = Meta

    def __init__(self):
        pass


In the above, type.__new__() is called and the fourth argument (which becomes the third when actually used) creates a __dict__ in Foo. But if I wanted to modify Meta to include __slots__, then I have no dictionary to pass on to type()'s __new__() function (as far as I know -- I haven't tested any of this yet, just pondering and trying to find some kind of a use-case scenario).

Edit: A quick, but untested guess, is to take a dict of the values to be put into the __slots__ variables and pass it to type.__new__(). Then add an __init__() to Meta that populates the __slots__ variables from the dict. Although, I am not certain how that dict would reach __init__(), because the declaration of __slots__ prevents __dict__ from being created unless __dict__ is defined in __slots__...

Upvotes: 19

Views: 4714

Answers (3)

More about slots defined in metaclasses.

if type's subclass defines non-empty __slots__, Python throws a TypeError because of some complicated implementation-related stuff.

In [1]:

class Meta(type):
    __slots__ = ('x')

---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-38-1b16edef8eca> in <module>()
----> 1 class Meta(type):
      2     __slots__ = ('x')

TypeError: nonempty __slots__ not supported for subtype of 'type'

Empty __slots__ on the other hand don't produce any errors, but seam to have no effect.

In [2]:

class Meta(type):
    __slots__ = ()

class Foo(metaclass=Meta):
    pass

type(Foo)

Out [2]:

__main__.Meta

In [3]:

Foo.y = 42
Foo.y

Out [3]:

42

In [4]:

Foo.__dict__

Out [4]:

mappingproxy({'y': 42, '__dict__': <attribute '__dict__' of 'Foo' objects>, '__doc__': None, '__weakref__': <attribute '__weakref__' of 'Foo' objects>, '__module__': '__main__'})

In [5]:

foo = Meta('foo', (), {})
type(foo).__slots__

Out [5]:

()

In [6]:

foo.x = 42
foo.x

Out [6]:

42

In [7]:

foo.__dict__

Out [7]:

mappingproxy({'__dict__': <attribute '__dict__' of 'foo' objects>, 'x': 42, '__module__': '__main__', '__doc__': None, '__weakref__': <attribute '__weakref__' of 'foo' objects>})

In [8]:

# Testing on non-metaclasses. Just in case.
class Bar:
    __slots__ = ()

b = Bar()
b.__dict__

---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-54-843730c66f3f> in <module>()
      3 
      4 b = Bar()
----> 5 b.__dict__

AttributeError: 'Bar' object has no attribute '__dict__'

Upvotes: 2

jjm
jjm

Reputation: 6198

You can't create a type with a non-empty __slots__ attribute. What you can do is insert a __slots__ attribute into the new class's dict, like this:

class Meta(type): 
    def __new__(cls, name, bases, dctn):
         dctn['__slots__'] = ( 'x', )
         return type.__new__(cls, name, bases, dctn)

 class Foo(object):
    __metaclass__ = Meta

    def __init__(self):
        pass 

Now Foo has slotted attributes:

foo = Foo() 
foo.y = 1

throws

 AttributeError: 'Foo' object has no attribute 'y'

Upvotes: 26

aquavitae
aquavitae

Reputation: 19114

dctn in your example of a metaclass is the class dictionary, not the instance dictionary. __slots__ replaces the instance dictionary. If you create two examples:

class Meta(type):
    def __new__(cls, name, bases, dctn):
        return type.__new__(cls, name, bases, dctn)

class Foo1(object):
    __metaclass__ = Meta

class Foo2(object):
    __metaclass__ = Meta
    __slots__ = ['a', 'b']

Then:

>>> f1 = Foo1()
>>> f2 = Foo2()
>>> f1.__dict__ is Foo1.__dict__
False
>>> f2.__dict__
Traceback (most recent call last):
    ...
AttributeError: 'Foo2' object has no attribute '__dict__'

Upvotes: 8

Related Questions