I have a class with __slots__
:
class A:
__slots__ = ('foo',)
If I create a subclass without specifying __slots__
, the subclass will have a __dict__
:
class B(A):
pass
print('__dict__' in dir(B)) # True
Is there any way to prevent B
from having a __dict__
without having to set __slots__ = ()
?
The answer of @AKX is almost correct. I think __prepare__
and a metaclass is indeed the way this can be solved quite easily.
Just to recap:
__slots__
key after the class body is executed then the class will use __slots__
instead of __dict__
. __prepare__
. So if we simply return a dictionary containing the key '__slots__'
from __prepare__
then the class will (if the '__slots__'
key isn't removed again during the evaluation of the class body) use __slots__
instead of __dict__
. Because __prepare__
just provides the initial namespace one can easily override the __slots__
or remove them again in the class body.
So a metaclass that provides __slots__
by default would look like this:
class ForceSlots(type):
@classmethod
def __prepare__(metaclass, name, bases, **kwds):
# calling super is not strictly necessary because
# type.__prepare() simply returns an empty dict.
# But if you plan to use metaclass-mixins then this is essential!
super_prepared = super().__prepare__(metaclass, name, bases, **kwds)
super_prepared['__slots__'] = ()
return super_prepared
So every class and subclass with this metaclass will (by default) have an empty __slots__
in their namespace and thus create a "class with slots" (except the __slots__
are removed on purpose).
Just to illustrate how this would work:
class A(metaclass=ForceSlots):
__slots__ = "a",
class B(A): # no __dict__ even if slots are not defined explicitly
pass
class C(A): # no __dict__, but provides additional __slots__
__slots__ = "c",
class D(A): # creates normal __dict__-based class because __slots__ was removed
del __slots__
class E(A): # has a __dict__ because we added it to __slots__
__slots__ = "__dict__",
Which passes the tests mentioned in AKZs answer:
assert "__dict__" not in dir(A)
assert "__dict__" not in dir(B)
assert "__dict__" not in dir(C)
assert "__dict__" in dir(D)
assert "__dict__" in dir(E)
And to verify that it works as expected:
# A has slots from A: a
a = A()
a.a = 1
a.b = 1 # AttributeError: 'A' object has no attribute 'b'
# B has slots from A: a
b = B()
b.a = 1
b.b = 1 # AttributeError: 'B' object has no attribute 'b'
# C has the slots from A and C: a and c
c = C()
c.a = 1
c.b = 1 # AttributeError: 'C' object has no attribute 'b'
c.c = 1
# D has a dict and allows any attribute name
d = D()
d.a = 1
d.b = 1
d.c = 1
# E has a dict and allows any attribute name
e = E()
e.a = 1
e.b = 1
e.c = 1
As pointed out in a comment (by Aran-Fey ) there is a difference between del __slots__
and adding __dict__
to the __slots__
:
There's a minor difference between the two options:
del __slots__
will give your class not only a__dict__
, but also a__weakref__
slot.
How about a metaclass like this and the __prepare__()
hook ?
import sys
class InheritSlots(type):
def __prepare__(name, bases, **kwds):
# this could combine slots from bases, I guess, and walk the base hierarchy, etc
for base in bases:
if base.__slots__:
kwds["__slots__"] = base.__slots__
break
return kwds
class A(metaclass=InheritSlots):
__slots__ = ("foo", "bar", "quux")
class B(A):
pass
assert A.__slots__
assert B.__slots__ == A.__slots__
assert "__dict__" not in dir(A)
assert "__dict__" not in dir(B)
print(sys.getsizeof(A()))
print(sys.getsizeof(B()))
For some reason, this still does print 64, 88
– maybe an inherited class's instances are always a little heavier than the base class itself?
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.