英文:
how to make abstract class that forces implementors to be dataclass
问题
以下是代码的翻译部分:
我有一个抽象类,对于任何实现它的 `T`,可以将 `List[T]` 转换成一个 CSV 文件字符串。它的代码如下:
```python
from abc import ABC, abstractmethod
from dataclasses import dataclass, fields
from typing import List, Type, TypeVar
T = TypeVar('T')
class CsvableDataclass(ABC):
@classmethod
def to_csv_header(cls) -> str:
return ','.join(f.name for f in fields(cls))
def to_csv_row(self) -> str:
return ','.join(self.format_field(f.name) for f in fields(self))
@abstractmethod
def format_field(self, field_name: str) -> str:
pass
# 使用上述方法实际创建一个 CSV 字符串
@staticmethod
def to_csv_str(t: Type[T], data: List[T]):
return '\n'.join([t.to_csv_header()] + [r.to_csv_row() for r in data])
@dataclass
class A(CsvableDataclass):
x: int
y: int
def format_field(self, field_name: str) -> str:
if field_name == 'x': return str(self.x)
if field_name == 'y': return str(self.y)
raise Exception(f"无效的字段名: {field_name}")
CsvableDataclass.to_csv_str(A, [A(1,2),A(3,4)])
# "x,y\n1,2\n3,4"
我正在使用 dataclasses
模块中的 fields()
函数来获取字段以创建 CSV 的标题行和所有其他行。fields()
仅适用于被 @dataclass
修饰的实例或类。
如何在类型注释中强制要求扩展 CsvableDataclass
的类必须是 @dataclass
呢?
英文:
I have an abstract class, which for any T
that implements it, lets you convert a List[T]
into a csv file string. It looks like this:
from abc import ABC, abstractmethod
from dataclasses import dataclass, fields
from typing import List, Type, TypeVar
T = TypeVar('T')
class CsvableDataclass(ABC):
@classmethod
def to_csv_header(cls) -> str:
return ','.join(f.name for f in fields(cls))
def to_csv_row(self) -> str:
return ','.join(self.format_field(f.name) for f in fields(self))
@abstractmethod
def format_field(self, field_name: str) -> str:
pass
# actually make a csv string using the methods above
@staticmethod
def to_csv_str(t: Type[T], data: List[T]):
return '\n'.join([t.to_csv_header()] + [r.to_csv_row() for r in data])
@dataclass
class A(CsvableDataclass):
x: int
y: int
def format_field(self, field_name: str) -> str:
if field_name == 'x': return str(self.x)
if field_name == 'y': return str(self.y)
raise Exception(f"invalid field_name: {field_name}")
CsvableDataclass.to_csv_str(A, [A(1,2),A(3,4)])
# "x,y\n1,2\n3,4"
I'm using fields()
from the dataclasses
module to get the fields to make the header row and all other rows of the csv. fields()
only works on instances or classes that are @dataclass
.
How do I enforce (in the type annotations) that a class that extends CsvableDataclass
must be a @dataclass
?
答案1
得分: 1
以下是您要求的翻译部分:
-
这很棘手 - 因为在数据类存在之后,没有一个"东西"可以将它们区分开来。
-
这就是
@dataclass
是装饰器而不是基类的原因之一:它会为应用它的类添加一些工具,但这些类可以是任何东西。 -
完全忘掉类型:就数据类而言,与类型无关的特殊之处就是它们识别
@dataclass
装饰器(并可以了解其他类似的装饰器),因此类型检查工具知道最终生成的类"更胜一筹",具有在源代码中没有明确表示的方法和功能。 -
对于您的需求,即能够重用
dataclasses.fields
,可能的解决方法是在您的类本身实现一个机制,可以自动使所有子类成为数据类。 -
但是,如果您的类的用户自己使用
@dataclass
(特别是使用不同的参数),可能会发生冲突。 -
否则,可以向您的类添加一个
__init_subclass__
方法,如下所示:
from dataclasses import dataclass, fields
...
class CVSAbleDataClass(...):
...
def __init_subclass__(cls, **kwargs):
super().__init_subclass__(**kwargs)
cls = dataclass(cls)
...
(顺便说一下,在您的下一个问题中,请随着代码一起包括导入语句:它们是代码的一部分,并为回答提供有价值的上下文。)
英文:
That is tricky -
Because there is not one "thing" that distinguises dataclasses after they exist.
That is of the reasons @dataclass
is a decorator and not a base class: it instruments the class where it is applied, but those classes can be anything.
Forget about typing altogether: there is nothing special in typing as far as dataclasses are concerned, except that they recognize the @dataclass
decorator (and can learn about other similar decorators), so that the type checking tools know the final resulting class is "more than meets the eye", and has methods and capabilities that are not explicit in the source code.
For your purposes, of being able to re-use dataclasses.fields
- what may work is to implement in your class itself a mechanism that will make all child classes dataclasses automatically.
But then, it might conflict if users of your class would use
@dataclass
themselves (and in particular, with different arguments)
Otherwise, add an __init_subclass__
method to your class that goes along this:
from dataclasses import dataclass, fields
...
class CVSAbleDataClass(...):
...
def __init_subclass__(cls, **kwargs):
super().__init_subclass__(**kwargs)
cls = dataclass(cls)
...
(and btw, in your next questions, do include the import statements along with your snippet: they are part of the code and provide valuable context for answering)
</details>
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论