考虑一个简单的数据类:
from ctypes import c_int32, c_int16
from dataclasses import dataclass
@dataclass
class MyClass:
field1: c_int32
field2: c_int16
根据文档,如果我们想使这个数据类兼容ctypes,我们必须像这样定义它:
import ctypes
from ctypes import Structure, c_int32, c_int16, sizeof
from dataclasses import dataclass, fields
@dataclass
class MyClass(ctypes.Structure):
_pack_ = 1
_fields_ = [("field1", c_int32),("field2", c_int16)]
print(ctypes.sizeof(MyClass))
但不幸的是,这个定义剥夺了我们数据类的便捷特性,即所谓的“dunder”方法。例如,constructor( __init__()) 和 string representative( __repr__()) 变得不可用:
inst = MyClass(c_int32(42), c_int16(43)) # will give error
问:在不丢失“dunder”方法的情况下,使数据类与 ctypes 兼容的最优雅和最惯用的方法是什么?
如果我们问我,这段代码乍一看似乎是可行的:
@dataclass
class MyClass(ctypes.Structure):
field1: c_int32
field2: c_int16
_pack_ = 1
MyClass._fields_ = [(field.name, field.type) for field in fields(MyClass)] #_pack_ is skipped
由于我是初学者,我不确定这段代码是否不会导致一些其他不明显的问题。
ctypes.Structure 和 dataclass 都具有一些类似的功能 - 但都不是明确为了与其他合作而构建的 - 因此我们必须制作这个桥接代码。
首先,
dataclass装饰器总是会尽量减少对类已有功能的破坏。由于Structurealready 提供了一种__init__方法,并且该方法可行,因此我们必须告诉 dataclass 将其保留在原处 - 只需将init=False参数传递给即可dataclass:Structure以这种方式创建的类将起作用,但不会有__repr__- dataclass 需要注释字段才能了解其内容。下面的装饰器可以代替
@dataclass:装饰器内部的少量逻辑只是为了能够将额外的参数传递给原始
dataclass调用——唯一重要的事情是设置字段.__annotation__并将init=False参数传递给数据类。但是,当注释语法到位时,数据类的出现时间比结构晚,并且声明类字段比使用
_fields_参数更方便。反向装饰器可以做你在问题第二部分中所做的事 - 只需传递init=False装饰器。(我不确定是否
_pack_可以在创建类后进行设置 - 请测试功能)当然,ctypes 结构有一些额外的功能,比如允许创建位域 - 这还不够 - 但它应该足以组成复杂的结构,而无需使用前向声明。
__repr__另一个选择是使用defined 而不是 use来定义基类dataclass。下面也处理类继承:输出: