【问题标题】:How to use `__slots__` with initialization of attributes?如何使用 `__slots__` 初始化属性?
【发布时间】:2017-06-13 02:14:30
【问题描述】:

我阅读了usage of slots 上的主要答案,它让我了解了如何以及在何处使用__slots__

现在,我将代码从 Python 2 移植到 Python 3,类似于以下内容:

class B(object):
    __slots__ = ('_fields')
    _fields = set()

但这会在 Python 2 上正常工作时给出错误 Python 3:

ValueError: '_fields' in __slots__ conflicts with class variable.

我把代码改成

class B(object):
    __slots__ = ('_fields')
    def __init__(self):
        _fields = set()

而且效果很好。我的问题是,它甚至是正确的变化吗?

正如我从原始代码中得到的那样,我想这是说不要使用 __dict__ 来节省内存或加快访问速度或其他任何原因,但同时也试图将属性类型指定为 _field 为放()。上面的改变是正确的表达方式还是会产生一些副作用。


进一步的实验: 我进一步试验了以下变体(在 Python 3 上):

import pdb

class A(object):
    a = set()

'''
class B(object):
    __slots__ = ('a')
    a = set()
'''

class C(object):
    __slots__ = ('a')
    def __init__(self):
        a = set()

class D(object):
    def __init__(self):
        __slots__ = ('a')
        a = set()

if __name__ == '__main__':
    #pdb.set_trace()
    x = A(); print(dir(x))
    #y = B()
    z = C(); print(dir(z))
    z1 = D(); print(dir(z1))

它给出了以下输出。

['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'a']


['__class__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__slots__', '__str__', '__subclasshook__', 'a']


['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__']

我们可以看到只有 C 对象显示正确的足迹,即没有 __dict__ 并且只有 __slots__ 。这不是我们想要的理想吗?对__weakref__ 的任何解释也会有所帮助。

同样在 Python 2 上,B 和 C 对象都显示相同的足迹。基于此,C 应该是正确的表达方式,因为它也在 Python 2 和 3 上编译。

【问题讨论】:

    标签: python initialization slots


    【解决方案1】:

    但这会在 Python 2 上正常工作时给出错误 Python 3:

    ValueError: '_fields' in __slots__ conflicts with class variable.

    虽然您在 Python2 中没有像在 Py3k 中那样在类创建/编译时遇到错误,但如果您尝试实际设置 _fields 的值,您会得到 AttributeError: 'C' object attribute '_fields' is read-only

    >>> class C(object):
    ...   __slots__ = ('_fields')
    ...   _fields = set()
    ...
    >>>
    >>> c = C()
    >>> c._fields
    set([])
    >>> c._fields = 'foo'
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    AttributeError: 'C' object attribute '_fields' is read-only
    >>>
    

    另外,见第四个note in the slots documentation

    __slots__ 通过为每个变量名创建描述符(实现描述符)在类级别实现。因此,类属性不能用于为__slots__定义的实例变量设置默认值;否则,类属性将覆盖描述符分配。


    写下你的修改:

    我把代码改成

    class B(object):
        __slots__ = ('_fields')
        def __init__(self):
            _fields = set()
    

    修改后的 B 类在 __init__() 内有一个 _fields,而不是 self._fields,因此它只是 init 中的局部变量,在类中的其他任何地方都无法访问。将其更改为:

     class B(object):
        __slots__ = ('_fields')
        def __init__(self):
            self._fields = set()
    

    要正确初始化_fields,请执行以下操作:

     class B(object):
         __slots__ = ('_fields')
         def __init__(self, _fields=None):
             if not _fields:
                 self._fields = set()
    

    进一步实验:

    在 D 类中,__slots__ 是仅在 __init()__ 内部的变量。这不是(特殊)类变量D.__slots__;甚至是实例变量self.__slots__。所以它有__dict__

    A 类没有,__dict__ 也有。

    C 类的 __slots__ 正确。

    【讨论】:

    • 谢谢。但是我们是否想在__init__ 中说以下内容而不是self._fields = _fields if not None else set()
    • if not _fields:boolean context 中检查_fields。因此,如果_fieldsNone,那么该表达式的计算结果为True,并且self._fields = set() 行将被执行。也可以使用if _fields is None: self._fields = set()。顺便说一句,在您评论中的示例中,not None 始终为 True。你可以把它写成 self._fields = _fields if _fields is not None else set() 或者更 Pythonic 的 self._fields = _fields if _fields else set()警告: 一个空的 set() 评估为 False,因此请使用适当的检查...
    • 警告: 一个空的 set() 评估为 False,因此使用另一个检查 set() 是否正常但 None 不正常。另外,不要使用mutable types as default argument values 初始化函数或方法;比如setdictlistAnother example here
    【解决方案2】:

    我个人发现解决此问题的最简单方法:

    class B(object):
        __slots__ = ('_fields')
        _fields: set()
    
        # Overridden in the derived classes
        def __init__(self, _fields=None):
        # your code
    

    【讨论】:

      【解决方案3】:

      (假设 Python3)

      __slots__ 中不必提及类属性。换句话说,即使该类派生自object,并且其名称未出现在类的__slots__ 中,也可以定义类属性。

      在您的情况下实现此目的的正确方法是:

      class B(object):
          __slots__ = ('x', 'y', 'z') # e.g. restrict instance attributes (if wanted)
          _fields = set()             # define the class attribute independently.
      

      【讨论】:

        【解决方案4】:

        我刚刚有一个(愚蠢的?)想法,我真的不确定这是否是“有效”的 Python,但它似乎有效(在 Python 3.7.7 中快速测试):

        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
            }
        

        请注意,我(错了吗?)在这里使用__slots__ 字典!

        magic = Magic()
        
        print(f"magic.foo = {magic.foo}")
        print(f"magic.bar = {magic.bar}")
        
        magic.foo = True
        magic.bar = 17
        

        有谁知道这样做有没有坏处?

        【讨论】:

        • 抱歉重复,但我刚刚从我自己的答案中创建了一个新的question,以获得有关此事的更多反馈。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2012-11-25
        • 1970-01-01
        • 1970-01-01
        • 2021-11-28
        • 2014-04-24
        • 2018-09-18
        • 1970-01-01
        相关资源
        最近更新 更多