La réponse de @AKX est presque correct. Je pense __prepare__
et une métaclasse est en effet le moyen de résoudre ce problème assez facilement.
Juste pour récapituler :
- Si l'espace de nom de la classe contient un
__slots__
après l'exécution du corps de la classe, alors la classe utilisera la touche __slots__
au lieu de __dict__
.
- On peut injecter des noms dans l'espace de nom de la classe avant le corps de la classe est exécuté en utilisant
__prepare__
.
Ainsi, si nous retournons simplement un dictionnaire contenant la clé '__slots__'
de __prepare__
alors la classe sera (si le '__slots__'
n'est pas supprimée lors de l'évaluation du corps de la classe). __slots__
au lieu de __dict__
. Parce que __prepare__
ne fournit que l'espace de nom initial, on peut facilement remplacer l'option __slots__
ou les supprimer à nouveau dans le corps de la classe.
Ainsi, une métaclasse qui fournit __slots__
par défaut ressemblerait à ceci :
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
Ainsi, chaque classe et sous-classe avec cette métaclasse aura (par défaut) une classe vide __slots__
dans leur espace de nom et ainsi créer une "classe avec des créneaux" (à l'exception de l'élément __slots__
sont volontairement supprimés).
Juste pour illustrer comment cela fonctionnerait :
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__",
Qui passe les tests mentionnés dans la réponse d'AKZ :
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)
Et pour vérifier qu'il fonctionne comme prévu :
# 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
Comme indiqué dans un commentaire (par Aran-Fey ) il y a une différence entre del __slots__
et en ajoutant __dict__
au __slots__
:
Il y a une petite différence entre les deux options : del __slots__
donnera à votre classe non seulement une __dict__
mais aussi un __weakref__
fente.