我目前正在尝试使用Python 3.7中引入的新数据类结构.我目前坚持尝试做一些父类的继承.看起来参数的顺序是由我当前的方法拙劣的,这样子类中的bool参数在其他参数之前传递.这导致类型错误.
from dataclasses import dataclass
@dataclass
class Parent:
name: str
age: int
ugly: bool = False
def print_name(self):
print(self.name)
def print_age(self):
print(self.age)
def print_id(self):
print(f'The Name is {self.name} and {self.name} is {self.age} year old')
@dataclass
class Child(Parent):
school: str
ugly: bool = True
jack = Parent('jack snr', 32, ugly=True)
jack_son = Child('jack jnr', 12, school = 'havard', ugly=True)
jack.print_id()
jack_son.print_id()
当我运行此代码时,我得到了这个TypeError
:
TypeError: non-default argument 'school' follows default argument
我该如何解决?
1> Martijn Piet..:
数据类组合属性的方式使您无法在基类中使用具有默认值的属性,然后在子类中使用没有默认(位置属性)的属性.
那是因为属性是从MRO的底部开始组合的,并按照首先看到的顺序构建属性的有序列表; 覆盖保留在原始位置.所以Parent
从一开始['name', 'age', 'ugly']
,ugly
默认情况下,然后Child
添加['school']
到该列表的末尾(ugly
已经在列表中).这意味着您最终得到['name', 'age', 'ugly', 'school']
并且因为school
没有默认值,这会导致无效的参数列表__init__
.
这在PEP-557数据类中有记录,在继承下:
当@dataclass
装饰器创建数据类时,它会在反向MRO中查看所有类的基类(即,object
从中开始),并且对于它找到的每个数据类,将该基类中的字段添加到有序类中字段映射.添加完所有基类字段后,它会将自己的字段添加到有序映射中.所有生成的方法都将使用这种组合的,计算的有序字段映射.由于字段是按插入顺序排列的,因此派生类会覆盖基类.
在规范下:
TypeError
如果没有默认值的字段在具有默认值的字段后面,则会引发.当这发生在单个类中时,或者作为类继承的结果时,都是如此.
你有几个选项可以避免这个问题.
第一个选项是使用单独的基类将具有默认值的字段强制到MRO顺序中的稍后位置.不惜一切代价,避免直接在要用作基类的类上设置字段,例如Parent
.
以下类层次结构有效:
# base classes with fields; fields without defaults separate from fields with.
@dataclass
class _ParentBase:
name: str
age: int
@dataclass
class _ParentDefaultsBase:
ugly: bool = False
@dataclass
class _ChildBase(_ParentBase):
school: str
@dataclass
class _ChildDefaultsBase(_ParentDefaultsBase):
ugly: bool = True
# public classes, deriving from base-with, base-without field classes
# subclasses of public classes should put the public base class up front.
@dataclass
class Parent(_ParentDefaultsBase, _ParentBase):
def print_name(self):
print(self.name)
def print_age(self):
print(self.age)
def print_id(self):
print(f"The Name is {self.name} and {self.name} is {self.age} year old")
@dataclass
class Child(Parent, _ChildDefaultsBase, _ChildBase):
pass
通过将字段拉出到单独的基类中,其中包含没有默认值的字段和具有默认值的字段,以及精心选择的继承顺序,您可以生成一个MRO,在没有默认值的情况下将所有字段放在默认值之前.反向MRO(忽略object
)Child
是:
_ParentBase
_ChildBase
_ParentDefaultsBase
_ChildDefaultsBase
Parent
请注意,Parent
这不会设置任何新字段,因此它在字段列表顺序中以"last"结尾并不重要.具有无默认值(_ParentBase
和_ChildBase
)的字段的类位于具有默认值(_ParentDefaultsBase
和_ChildDefaultsBase
)的字段的类之前.
其结果是Parent
,并Child
有一个健全的领域类年纪大了,而Child
仍然是一个子类Parent
:
>>> from inspect import signature
>>> signature(Parent)
None>
>>> signature(Child)
None>
>>> issubclass(Child, Parent)
True
所以你可以创建这两个类的实例:
>>> jack = Parent('jack snr', 32, ugly=True)
>>> jack_son = Child('jack jnr', 12, school='havard', ugly=True)
>>> jack
Parent(name='jack snr', age=32, ugly=True)
>>> jack_son
Child(name='jack jnr', age=12, school='havard', ugly=True)
另一种选择是仅使用具有默认值的字段; 你仍然可以school
通过提出一个错误来提供错误__post_init__
:
_no_default = object()
@dataclass
class Child(Parent):
school: str = _no_default
ugly: bool = True
def __post_init__(self):
if self.school is _no_default:
raise TypeError("__init__ missing 1 required argument: 'school'")
但这确实改变了野外秩序; school
结束后ugly
:
None>
并且类型提示检查器会抱怨_no_default
不是字符串.
您也可以使用attrs
项目,这是受到启发的项目dataclasses
.它使用不同的继承合并策略; 它拉覆盖在子类中的字段列表的末尾字段,因此['name', 'age', 'ugly']
在Parent
类成为['name', 'age', 'school', 'ugly']
在Child
类; 通过使用默认值覆盖该字段,attrs
允许覆盖而无需进行MRO舞蹈.
attrs
支持定义没有类型提示的字段,但可以通过设置以下内容来坚持支持的类型提示模式auto_attribs=True
:
import attr
@attr.s(auto_attribs=True)
class Parent:
name: str
age: int
ugly: bool = False
def print_name(self):
print(self.name)
def print_age(self):
print(self.age)
def print_id(self):
print(f"The Name is {self.name} and {self.name} is {self.age} year old")
@attr.s(auto_attribs=True)
class Child(Parent):
school: str
ugly: bool = True
2> Patrick Haug..:
您看到此错误,因为在具有默认值的参数之后添加了没有默认值的参数.继承字段到数据类的插入顺序与方法解析顺序相反,这意味着Parent
字段首先出现,即使它们的子节点稍后写入.
PEP-557的一个例子- 数据类:
@dataclass
class Base:
x: Any = 15.0
y: int = 0
@dataclass
class C(Base):
z: int = 10
x: int = 15
最终的字段列表按顺序排列x, y, z
.最终类型x
是int
,如类中所指定C
.
不幸的是,我认为没有办法解决这个问题.我的理解是,如果父类有一个默认参数,那么没有子类可以有非默认参数.