使用dataclass时子类重写父类属性setter触发AttributeError
解决dataclass子类重写父类属性setter的问题
你用dataclass定义了基类SimpleUrl,想在子类FullUrl中重写url属性的setter,但运行代码时触发AttributeError: type object 'SimpleUrl' has no attribute 'url'错误。
原代码
from dataclasses import dataclass, field @dataclass(repr=False, eq=False) class SimpleUrl: id: int # pylint: disable=invalid-name url: str def __eq__(self, ins: object) -> bool: return isinstance(ins, SimpleUrl) and (self.url == ins.url) def __iter__(self) -> iter: return iter((self.id, self.url)) @dataclass(repr=False, eq=False) class FullUrl(SimpleUrl): ssl: bool = field(init=False, default=None) @SimpleUrl.url.setter def url(self, url: str) -> None: self.url = url self.ssl = url.startswith('https://') fu = FullUrl('https://www.google.com') print(fu.ssl)
错误信息
Exception has occurred: AttributeError type object 'SimpleUrl' has no attribute 'url' File "D:\projects\tests\test_urls.py", line 22, in FullUrl @SimpleUrl.url.setter ^^^^^^^^^^^^^ File "D:\projects\tests\test_urls.py", line 18, in <module> class FullUrl(SimpleUrl): AttributeError: type object 'SimpleUrl' has no attribute 'url'
错误原因
- dataclass自动生成的属性是实例属性,不是类级别的
property,所以SimpleUrl类本身没有url这个属性,无法通过@SimpleUrl.url.setter语法重写。 - 即使能定义setter,直接写
self.url = url会触发无限递归——赋值操作会再次调用当前setter方法。
解决方案
方案一:父类显式封装url为property
修改父类,将url通过property封装,用私有字段存储实际值,子类可安全重写setter:
from dataclasses import dataclass, field @dataclass(repr=False, eq=False) class SimpleUrl: id: int # pylint: disable=invalid-name url: str def __post_init__(self): # 将dataclass初始化的url值存入私有字段 self._url = self.url # 删除自动生成的实例属性,让property生效 del self.url @property def url(self) -> str: return self._url @url.setter def url(self, value: str) -> None: self._url = value def __eq__(self, ins: object) -> bool: return isinstance(ins, SimpleUrl) and (self.url == ins.url) def __iter__(self) -> iter: return iter((self.id, self.url)) @dataclass(repr=False, eq=False) class FullUrl(SimpleUrl): ssl: bool = field(init=False, default=None) def __post_init__(self): super().__post_init__() # 初始化时设置ssl状态 self.ssl = self.url.startswith('https://') @SimpleUrl.url.setter def url(self, url: str) -> None: # 调用父类setter设置核心值 super().url = url # 更新ssl状态 self.ssl = url.startswith('https://') # 正确实例化(传入id和url两个参数) fu = FullUrl(1, 'https://www.google.com') print(fu.ssl) # 输出True # 修改url时自动更新ssl fu.url = 'http://example.com' print(fu.ssl) # 输出False
方案二:子类独立封装url属性(不修改父类)
如果不想改动父类,子类可自行定义url的property,覆盖父类的实例属性:
from dataclasses import dataclass, field @dataclass(repr=False, eq=False) class SimpleUrl: id: int # pylint: disable=invalid-name url: str def __eq__(self, ins: object) -> bool: return isinstance(ins, SimpleUrl) and (self.url == ins.url) def __iter__(self) -> iter: return iter((self.id, self.url)) @dataclass(repr=False, eq=False) class FullUrl(SimpleUrl): ssl: bool = field(init=False, default=None) # 子类私有字段存储url值 _url: str = field(init=False) def __post_init__(self): # 将父类初始化的url值存入子类私有字段 self._url = self.url # 删除父类的实例属性,让子类property生效 del self.url # 初始化ssl状态 self.ssl = self._url.startswith('https://') @property def url(self) -> str: return self._url @url.setter def url(self, url: str) -> None: self._url = url self.ssl = url.startswith('https://') # 正确实例化(传入id和url两个参数) fu = FullUrl(1, 'https://www.google.com') print(fu.ssl) # 输出True
额外提示
原代码中FullUrl('https://www.google.com')会触发参数错误,因为父类SimpleUrl需要id和url两个必填参数,实例化时必须同时传入。
内容的提问来源于stack exchange,提问作者Manu
相关产品推荐
相关产品推荐

