我希望我的类构造函数接受变量输入,然后将它们规范化以进行存储和访问。例如:
class Ticket:
def __init__(self, number: int | str):
self.number: int = int(number)
# so that it's flexible in creation:
t = Ticket(6)
t = Ticket('7')
# but consistent when accessed:
isinstance(t.number, int) # True
我不知道正确的 OOP 术语,但我想确定我的类的接口?签名?正确反映它将接受
.number
作为 int 或 string,但访问 .number
将始终给出 int。
上面的方法有效(尽管我愿意接受建议),但是尝试对数据类执行相同的操作会在 Pylance 中出现类型错误:
@dataclass
class Ticket:
number: int | str
#^^^^^^ Pylance: Declaration "number" is obscured by a declaration of the same name
def __post_init__(self):
self.number: int = int(self.number)
这个问题在数据类版本中可以修复吗?或者只是数据类的限制?如果可能的话,我想保留数据类的其他好处。
没有一种超级漂亮的方法可以做到这一点;同一事物的类级别注释和
__post_init__
级别注释是冲突的,因此以静态分析可接受的方式处理它的唯一方法就是使它们成为不同的事物。您可以通过以下方式执行此操作:
InitVar
,为初始化提供的东西,但这不是对象的属性init=False
,因此它不接受初始化,但是一个属性在一起,你最终会得到这样的结果:
from dataclasses import dataclass, field, InitVar
@dataclass
class Ticket:
number: int = field(init=False) # The actual attribute; not accepted by generated __init__
# as an argument, only an int
numinit: InitVar[int | str] # The argument, that's not an attribute, and can be int or str
def __post_init__(self, numinit): # InitVar fields are passed to __post_init__
self.number = int(numinit) # And we can use them to initialize the uninitialized
这并不完美;名称有不同(初始化参数也不能被命名为
number
,至少不是没有一些可怕的黑客行为,这比一开始就简单地编写自己的__init__
要糟糕得多)。如果名称需要匹配,您可以自己写__init__
。当然,dataclass
不会为你做这件事很烦人,但你仍然会得到所有other生成的代码(例如__repr__
,__eq__
),所以对于像这样的简单情况来说并不可怕(它是比 InitVar
更简单,不过如果您有很多属性,并且只需要一个 InitVar
,那么 InitVar
看起来会更好)。
@dataclass
class Ticket:
number: int
def __init__(self, number: int | str): # Accept as union type
self.number = int(numinit) # Convert to single type