Reputation: 687
While searching for a convenient method to initialize slots, I had the stupid? idea of wrongly? using __slots__
dictionaries as shown below.
Note: There is a related question on SO where I've previously posted my idea as answer, but I though it might be more useful to create a new question from it as I'd really like to get more feedback.
So I'd appreciate any notes/advice/issues for the following "trick":
class Slotted:
__slots__ = {}
def __new__(cls, *args, **kwargs):
inst = super().__new__(cls)
for key, value in inst.__slots__.items():
setattr(inst, key, value)
return inst
class Magic(Slotted):
__slots__ = {
"foo": True,
"bar": 17
}
magic = Magic()
print(f"magic.foo = {magic.foo}")
print(f"magic.bar = {magic.bar}")
magic.foo = True
magic.bar = 17
Is it ok/safe to do this? Are there any drawbacks or possible probelms, etc.?
Edit:
After Alex Waygood mentioned the documentation purpose in Python 3.8+, I came up with an extension that also includes a correction for subclassing further - now it's gotten a bit lengthy though:
class Slot(str):
__slots__ = ["init"]
def __new__(cls, init, doc=""):
obj = str.__new__(cls, doc)
obj.init = init
return obj
def __call__(self):
return self.init
class Slotted:
__slots__ = {}
def __new__(cls, *args, **kwargs):
obj = super().__new__(cls)
for base in reversed(cls.__mro__[:-1]):
if isinstance(base.__slots__, dict):
for key, value in base.__slots__.items():
if isinstance(value, Slot):
setattr(obj, key, value())
else:
raise TypeError(
f'Value for slot "{key}" must'
f' be of type "{Slot.__name__}"'
)
return obj
class Magic(Slotted):
"""This class is not so boring any more"""
__slots__ = {
"foo": Slot(2, doc="Some quite interesting integer"),
"bar": Slot(3.1416, doc="Some very exciting float")
}
help(Magic)
magic = Magic()
print(f"magic.__slots__ = {magic.__slots__}")
print(f"magic.foo = {magic.foo}")
print(f"magic.bar = {magic.bar}")
Help on class Magic in module __main__:
class Magic(Slotted)
| Magic(*args, **kwargs)
|
| This class is not so boring any more
|
| Method resolution order:
| Magic
| Slotted
| builtins.object
|
| Data descriptors defined here:
|
| bar
| Some very exciting float
|
| foo
| Some quite interesting integer
|
| ----------------------------------------------------------------------
| Static methods inherited from Slotted:
|
| __new__(cls, *args, **kwargs)
| Create and return a new object. See help(type) for accurate signature.
magic.__slots__ = {'foo': 'Some quite interesting integer', 'bar': 'Some very exciting float'}
magic.foo = 2
magic.bar = 3.1416
Upvotes: 3
Views: 447
Reputation: 7569
As far as I know, the intended usage of the ability to define __slots__
as a dict
is for documentation purposes.
(I don't know where, if anywhere, this is documented, nor when it was added to Python. I do know that this behaviour is consistent across Python 3.8, 3.9, 3.10, and indeed 3.11 alpha 0 as of 14/10/2021. I haven't tested it on Python <= 3.7.)
If I have a class Foo
, like so:
class Foo:
"""The Foo class is for doing Foo-y things (obviously)."""
__slots__ = {
'bar': 'Some information about the bar attribute',
'baz': 'Some information about the baz attribute'
}
Then calling help(Foo)
in the interactive terminal results in the following output:
>>> help(Foo)
Help on class Foo in module __main__:
class Foo(builtins.object)
| The Foo class is for doing Foo-y things (obviously).
|
| Data descriptors defined here:
|
| bar
| Some information about the bar attribute
|
| baz
| Some information about the baz attribute
If I call help()
on your Magic
class, however, I get the following output:
>>> help(Magic)
Help on class Magic in module __main__:
class Magic(Slotted)
| Magic(*args, **kwargs)
|
| Method resolution order:
| Magic
| Slotted
| builtins.object
|
| Data descriptors defined here:
|
| bar
|
| foo
|
| ----------------------------------------------------------------------
| Static methods inherited from Slotted:
|
| __new__(cls, *args, **kwargs)
| Create and return a new object. See help(type) for accurate signature.
Having said that -- I don't think there's anything anywhere that says that you can't do the kind of thing you propose in your question. It seems to work fine, so if you don't care much about the documentation of your class, and it makes your code more DRY, I say go for it!
Upvotes: 4
Reputation: 104802
The docs say that any iterable containing strings is allowed for __slots__
, but it has a specific warning about mappings:
Any non-string iterable may be assigned to
__slots__
. Mappings may also be used; however, in the future, special meaning may be assigned to the values corresponding to each key.
I'm not aware of any active proposals to add such special meaning to a mapping used for __slots__
, but that doesn't mean one might not be created in the future. I would keep an eye out for deprecation warnings as you use this code in future releases!
Upvotes: 2