如何创建一个抽象类,强制实现者成为数据类

huangapple go评论62阅读模式
英文:

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>



huangapple
  • 本文由 发表于 2023年6月5日 23:52:54
  • 转载请务必保留本文链接:https://go.coder-hub.com/76408114.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定