Reputation: 2428
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
Reputation: 3893
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
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
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