How to dynamically change __slots__ attribute?

后端 未结 4 890
梦毁少年i
梦毁少年i 2020-12-06 13:27

Suppose I have a class with __slots__

class A:
    __slots__ = [\'x\']

a = A()
a.x = 1   # works fine
a.y = 1   # AttributeError (as expected)
         


        
4条回答
  •  孤城傲影
    2020-12-06 14:03

    You cannot dynamically alter the __slots__ attribute after creating the class, no. That's because the value is used to create special descriptors for each slot. From the __slots__ documentation:

    __slots__ are implemented at the class level by creating descriptors (Implementing Descriptors) for each variable name. As a result, class attributes cannot be used to set default values for instance variables defined by __slots__; otherwise, the class attribute would overwrite the descriptor assignment.

    You can see the descriptors in the class __dict__:

    >>> class A:
    ...     __slots__ = ['x']
    ... 
    >>> A.__dict__
    mappingproxy({'__module__': '__main__', '__doc__': None, 'x': , '__slots__': ['x']})
    >>> A.__dict__['x']
    
    >>> a = A()
    >>> A.__dict__['x'].__get__(a, A)
    Traceback (most recent call last):
      File "", line 1, in 
    AttributeError: x
    >>> A.__dict__['x'].__set__(a, 'foobar')
    >>> A.__dict__['x'].__get__(a, A)
    'foobar'
    >>> a.x
    'foobar'
    

    You cannot yourself create these additional descriptors. Even if you could, you cannot allocate more memory space for the extra slot references on the instances produced for this class, as that's information stored in the C struct for the class, and not in a manner accessible to Python code.

    That's all because __slots__ is only an extension of the low-level handling of the elements that make up Python instances to Python code; the __dict__ and __weakref__ attributes on regular Python instances were always implemented as slots:

    >>> class Regular: pass
    ... 
    >>> Regular.__dict__['__dict__']
    
    >>> Regular.__dict__['__weakref__']
    
    >>> r = Regular()
    >>> Regular.__dict__['__dict__'].__get__(r, Regular) is r.__dict__
    True
    

    All the Python developers did here was extend the system to add a few more of such slots using arbitrary names, with those names taken from the __slots__ attribute on the class being created, so that you can save memory; dictionaries take more memory than simple references to values in slots do. By specifying __slots__ you disable the __dict__ and __weakref__ slots, unless you explicitly include those in the __slots__ sequence.

    The only way to extend slots then is to subclass; you can dynamically create a subclass with the type() function or by using a factory function:

    def extra_slots_subclass(base, *slots):
        class ExtraSlots(base):
            __slots__ = slots
        ExtraSlots.__name__ = base.__name__
        return ExtraSlots
    

提交回复
热议问题