如何使用`__slots__`初始化属性?

时间:2017-01-27 11:46:43

标签: python initialization slots

我通读了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__来保存内存或更快的访问权限或任何原因,但同时也试图指定属性类型{{ 1}} as set()。上述变化可以说是正确的方式,也可能会产生一些副作用。

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

_field

它给出了以下输出。

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))

我们可以看到只有C对象显示正确的足迹,即没有['__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__'] 而只有__dict__。难道不是我们想要的理想选择吗?对__slots__的任何解释也会有所帮助。

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

3 个答案:

答案 0 :(得分:3)

  

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

     

ValueError: '_fields' in __slots__ conflicts with class variable

虽然你没有像创建Py3k那样在类创建/编译时在Python2中收到错误,但如果你试图实际设置_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属性将覆盖描述符赋值。

你的修改:

  

我将代码更改为

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

修改后的B类在_fields内部__init__(),而不是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__

答案 1 :(得分:0)

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

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

    # Overridden in the derived classes
    def __init__(self, _fields=None):
    # your code

答案 2 :(得分:0)

(假设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.