在继承的数据类中使用 __new__
Using __new__ in inherited dataclasses
假设我有以下代码用于处理个人和国家之间的链接:
from dataclasses import dataclass
@dataclass
class Country:
iso2 : str
iso3 : str
name : str
countries = [ Country('AW','ABW','Aruba'),
Country('AF','AFG','Afghanistan'),
Country('AO','AGO','Angola')]
countries_by_iso2 = {c.iso2 : c for c in countries}
countries_by_iso3 = {c.iso3 : c for c in countries}
@dataclass
class CountryLink:
person_id : int
country : Country
country_links = [ CountryLink(123, countries_by_iso2['AW']),
CountryLink(456, countries_by_iso3['AFG']),
CountryLink(789, countries_by_iso2['AO'])]
print(country_links[0].country.name)
一切正常,但我决定让它不那么笨拙,以便能够处理不同形式的输入。我还想使用 __new__
来确保我们每次都获得有效的 ISO 代码,并且我想反对在这种情况下无法创建。因此,我添加了几个继承自此的新 类:
@dataclass
class CountryLinkFromISO2(CountryLink):
def __new__(cls, person_id : int, iso2 : str):
if iso2 not in countries_by_iso2:
return None
new_obj = super().__new__(cls)
new_obj.country = countries_by_iso2[iso2]
return new_obj
@dataclass
class CountryLinkFromISO3(CountryLink):
def __new__(cls, person_id : int, iso3 : str):
if iso3 not in countries_by_iso3:
return None
new_obj = super().__new__(cls)
new_obj.country = countries_by_iso3[iso3]
return new_obj
country_links = [ CountryLinkFromISO2(123, 'AW'),
CountryLinkFromISO3(456, 'AFG'),
CountryLinkFromISO2(789, 'AO')]
乍一看似乎可行,但后来我 运行 遇到了问题:
a = CountryLinkFromISO2(123, 'AW')
print(type(a))
print(a.country)
print(type(a.country))
returns:
<class '__main__.CountryLinkFromISO2'>
AW
<class 'str'>
继承的对象具有正确的类型,但它的属性 country
只是一个字符串,而不是我期望的 Country
类型。我在 __new__
中输入了打印语句来检查 new_obj.country
的类型,它在 return
行之前是正确的。
我想要实现的是让 a
成为类型 CountryLinkFromISO2
的对象,它将继承我对 CountryLink
所做的更改,并使其具有属性 country
是从字典 countries_by_iso2
中取出来的。我怎样才能做到这一点?
仅仅因为数据class 在幕后进行,并不意味着您 class 没有 __init__()
。他们这样做了,看起来像:
def __init__(self, person_id: int, country: Country):
self.person_id = person_id
self.country = country
当您创建 class 时:
CountryLinkFromISO2(123, 'AW')
"AW"
字符串被传递给 __init__()
并将值设置为字符串。
以这种方式使用 __new__()
是脆弱的,并且从构造函数中 returning None 是相当不符合 Python 风格的(imo)。也许您最好制作一个 return 是 None
或您想要的 class 的实际工厂函数。那么你根本不需要搞乱__new__()
。
@dataclass
class CountryLinkFromISO2(CountryLink):
@classmethod
def from_country_code(cls, person_id : int, iso2 : str):
if iso2 not in countries_by_iso2:
return None
return cls(person_id, countries_by_iso2[iso2])
a = CountryLinkFromISO2.from_country_code(123, 'AW')
如果由于某种原因 需要 与 __new__()
一起工作,您可以 return None
来自新的当没有匹配时,并且将国家/地区设置为 __post_init__()
:
@dataclass
class CountryLinkFromISO2(CountryLink):
def __new__(cls, person_id : int, iso2 : str):
if iso2 not in countries_by_iso2:
return None
return super().__new__(cls)
def __post_init__(self):
self.country = countries_by_iso2[self.country]
您看到的行为是因为数据类在 __init__
中设置了它们的字段,这发生在 __new__
具有 运行.
之后
解决这个问题的 Pythonic 方法是提供一个备用构造函数。我不会做子类,因为它们只用于它们的构造函数。
例如:
@dataclass
class CountryLink:
person_id: int
country: Country
@classmethod
def from_iso2(cls, person_id: int, country_code: str):
try:
return cls(person_id, countries_by_iso2[country_code])
except KeyError:
raise ValueError(f'invalid ISO2 country code {country_code!r}') from None
@classmethod
def from_iso3(cls, person_id: int, country_code: str):
try:
return cls(person_id, countries_by_iso3[country_code])
except KeyError:
raise ValueError(f'invalid ISO3 country code {country_code!r}') from None
country_links = [ CountryLink.from_iso2(123, 'AW'),
CountryLink.from_iso3(456, 'AFG'),
CountryLink.from_iso2(789, 'AO')]
假设我有以下代码用于处理个人和国家之间的链接:
from dataclasses import dataclass
@dataclass
class Country:
iso2 : str
iso3 : str
name : str
countries = [ Country('AW','ABW','Aruba'),
Country('AF','AFG','Afghanistan'),
Country('AO','AGO','Angola')]
countries_by_iso2 = {c.iso2 : c for c in countries}
countries_by_iso3 = {c.iso3 : c for c in countries}
@dataclass
class CountryLink:
person_id : int
country : Country
country_links = [ CountryLink(123, countries_by_iso2['AW']),
CountryLink(456, countries_by_iso3['AFG']),
CountryLink(789, countries_by_iso2['AO'])]
print(country_links[0].country.name)
一切正常,但我决定让它不那么笨拙,以便能够处理不同形式的输入。我还想使用 __new__
来确保我们每次都获得有效的 ISO 代码,并且我想反对在这种情况下无法创建。因此,我添加了几个继承自此的新 类:
@dataclass
class CountryLinkFromISO2(CountryLink):
def __new__(cls, person_id : int, iso2 : str):
if iso2 not in countries_by_iso2:
return None
new_obj = super().__new__(cls)
new_obj.country = countries_by_iso2[iso2]
return new_obj
@dataclass
class CountryLinkFromISO3(CountryLink):
def __new__(cls, person_id : int, iso3 : str):
if iso3 not in countries_by_iso3:
return None
new_obj = super().__new__(cls)
new_obj.country = countries_by_iso3[iso3]
return new_obj
country_links = [ CountryLinkFromISO2(123, 'AW'),
CountryLinkFromISO3(456, 'AFG'),
CountryLinkFromISO2(789, 'AO')]
乍一看似乎可行,但后来我 运行 遇到了问题:
a = CountryLinkFromISO2(123, 'AW')
print(type(a))
print(a.country)
print(type(a.country))
returns:
<class '__main__.CountryLinkFromISO2'>
AW
<class 'str'>
继承的对象具有正确的类型,但它的属性 country
只是一个字符串,而不是我期望的 Country
类型。我在 __new__
中输入了打印语句来检查 new_obj.country
的类型,它在 return
行之前是正确的。
我想要实现的是让 a
成为类型 CountryLinkFromISO2
的对象,它将继承我对 CountryLink
所做的更改,并使其具有属性 country
是从字典 countries_by_iso2
中取出来的。我怎样才能做到这一点?
仅仅因为数据class 在幕后进行,并不意味着您 class 没有 __init__()
。他们这样做了,看起来像:
def __init__(self, person_id: int, country: Country):
self.person_id = person_id
self.country = country
当您创建 class 时:
CountryLinkFromISO2(123, 'AW')
"AW"
字符串被传递给 __init__()
并将值设置为字符串。
以这种方式使用 __new__()
是脆弱的,并且从构造函数中 returning None 是相当不符合 Python 风格的(imo)。也许您最好制作一个 return 是 None
或您想要的 class 的实际工厂函数。那么你根本不需要搞乱__new__()
。
@dataclass
class CountryLinkFromISO2(CountryLink):
@classmethod
def from_country_code(cls, person_id : int, iso2 : str):
if iso2 not in countries_by_iso2:
return None
return cls(person_id, countries_by_iso2[iso2])
a = CountryLinkFromISO2.from_country_code(123, 'AW')
如果由于某种原因 需要 与 __new__()
一起工作,您可以 return None
来自新的当没有匹配时,并且将国家/地区设置为 __post_init__()
:
@dataclass
class CountryLinkFromISO2(CountryLink):
def __new__(cls, person_id : int, iso2 : str):
if iso2 not in countries_by_iso2:
return None
return super().__new__(cls)
def __post_init__(self):
self.country = countries_by_iso2[self.country]
您看到的行为是因为数据类在 __init__
中设置了它们的字段,这发生在 __new__
具有 运行.
解决这个问题的 Pythonic 方法是提供一个备用构造函数。我不会做子类,因为它们只用于它们的构造函数。
例如:
@dataclass
class CountryLink:
person_id: int
country: Country
@classmethod
def from_iso2(cls, person_id: int, country_code: str):
try:
return cls(person_id, countries_by_iso2[country_code])
except KeyError:
raise ValueError(f'invalid ISO2 country code {country_code!r}') from None
@classmethod
def from_iso3(cls, person_id: int, country_code: str):
try:
return cls(person_id, countries_by_iso3[country_code])
except KeyError:
raise ValueError(f'invalid ISO3 country code {country_code!r}') from None
country_links = [ CountryLink.from_iso2(123, 'AW'),
CountryLink.from_iso3(456, 'AFG'),
CountryLink.from_iso2(789, 'AO')]