从Python3.7的数据类中删除对__slots__
的直接支持。
尽管如此,__slots__
仍然可以用于数据类:
from dataclasses import dataclass
@dataclass
class C():
__slots__ = "x"
x: int
但是,由于
__slots__
的工作方式,无法为数据类字段分配默认值:from dataclasses import dataclass
@dataclass
class C():
__slots__ = "x"
x: int = 1
这将导致错误:
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ValueError: 'x' in __slots__ conflicts with class variable
如何使
__slots__
和默认dataclass
字段一起工作?
最佳答案
这个问题不是数据类独有的。任何冲突的类属性都将在一个槽上踩踏:
class Failure:
__slots__ = tuple("xyz")
x=1
# ERROR
这就是插槽的工作原理。为了防止这种情况发生,必须在实例化类对象之前更改类命名空间,以便在类对象成员中没有两个竞争对象竞争同一时隙:
指定(默认)值(或字段对象)
由slots机器创建的成员描述符
因此,父类上的
__init_subclass__
方法是不够的,类装饰器也不够,因为在这两种情况下,类对象都已创建。在这种情况下,槽机械被改变以允许更大的灵活性之前,我们唯一的选择是使用一个元类。
为解决此问题而编写的任何元类至少必须:
从命名空间中删除冲突的类属性/成员
实例化类对象以创建插槽描述符
保存对插槽描述符的引用
将先前移除的成员及其值放回类中(以便
__dict__
机器可以找到它们)将类对象传递给
dataclass
decorator将插槽描述符恢复到各自的位置
还要考虑大量的拐角情况(例如,如果有
dataclass
槽该怎么办)至少可以说,这是一项极其复杂的工作。像下面这样定义类(这样就不会发生冲突)会更容易一些,然后在之后对其进行修改,这样dataclass字段就有了所需的默认值。
@dataclass
class C:
__slots__ = "x"
x: int # field(default = 1)
修改很简单。更改
__dict__
签名以反映所需的默认值,然后更改__init__
以反映默认值的存在。from functools import wraps
def change_init_signature(init):
@wraps(init)
def __init__(self, x=1):
init(self,x)
return __init__
C.__init__ = change_init_signature(C.__init__)
C.__dataclass_fields__["x"].default = 1
测试:
>>> C()
C(x=1)
>>> C(2)
C(x=2)
>>> C.x
<member 'x' of 'C' objects>
>>> vars(C())
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: vars() argument must have __dict__ attribute
它起作用了!
通过一些努力,可以使用一个所谓的
__dataclass_fields__
decorator以上述方式自动更改类。这将需要与dataclasses api有所不同-可能类似于:@slotted_dataclass(x:int=field(default=1))
class C:
__slots__="x"
同样的事情也可以通过父类上的
slotted_dataclass
方法来实现:class SlottedDataclass:
def __init_subclass__(cls, **kwargs):
cls.__init_subclass__()
# make the class changes here
class C(SlottedDataclass, x=1):
__slots__ = "x"
x: int
解决这个问题的另一种可能的方法可能是将
__init_subclass__
实用程序函数添加到dataclasses api(或者添加到具有自己的装饰器的自定义、单独的api)。可能会有如下效果:
@slotted_dataclass
class C:
__slots__ = dataclass_slots(x=field(default=1))
x: int
dataclass_slots
函数返回的对象将是iterable,并允许现有的slots机器工作。不过,它也允许dataclass_slots
装饰器在之后适当地创建字段对象、方法等。
关于python - 如何使__slots__更好地使用数据类?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/50180735/