简体   繁体   中英

Is it valid Python to use a __slots__ dictionary for initialization purposes?

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

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!

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!

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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM