python - 如何使__slots__更好地使用数据类?

标签 python python-3.x slots python-dataclasses

从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__机器可以找到它们)
将类对象传递给dataclassdecorator
将插槽描述符恢复到各自的位置
还要考虑大量的拐角情况(例如,如果有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/

相关文章:

c++ - Qt C++ WebKit windowCloseRequested 信号

Python 排序方法在用户定义的函数中不起作用

python-3.x - 从 Python 3 for 循环打印时是否可以插入除最后一个分隔符之外的所有分隔符?

python - fping 在 python3 中使用 IP 列表

python - 使用 Scikit-learn 进行 RBF 拟合时出现 MemoryError

javascript - Slots 不适用于没有 shadow dom 的 html Web 组件

java - 协助java中的slots程序

python - 禁用 Shutil.copy 中的 SameFileError 异常

python - Django-registration - 一些激活

python - 抽象基类的注册方法实际上做了什么?