<p>数据类组合属性的方式使您无法在基类中使用具有默认值的属性,然后在子类中使用不具有默认值(位置属性)的属性。</p>
<p>这是因为属性是从MRO的底部开始组合的,并按照第一个看到的顺序构建属性的有序列表;覆盖保留在其原始位置。所以<code>Parent</code>以<code>['name', 'age', 'ugly']</code>开始,其中<code>ugly</code>有默认值,然后<code>Child</code>将<code>['school']</code>添加到列表的末尾(列表中已经有<code>ugly</code>)。这意味着您以<code>['name', 'age', 'ugly', 'school']</code>结束,并且由于<code>school</code>没有默认值,这将导致<code>__init__</code>的参数列表无效。</p>
<p>这记录在<a href="https://www.python.org/dev/peps/pep-0557/" rel="noreferrer">PEP-557 <em>Dataclasses</em></a>中的<a href="https://www.python.org/dev/peps/pep-0557/#inheritance" rel="noreferrer"><em>inheritance</em></a>下:</p>
<blockquote>
<p>When the Data Class is being created by the <code>@dataclass</code> decorator, it looks through all of the class's base classes in reverse MRO (that is, starting at <code>object</code>) and, for each Data Class that it finds, adds the fields from that base class to an ordered mapping of fields. After all of the base class fields are added, it adds its own fields to the ordered mapping. All of the generated methods will use this combined, calculated ordered mapping of fields. Because the fields are in insertion order, derived classes override base classes.</p>
</blockquote>
<p>在<a href="https://www.python.org/dev/peps/pep-0557/#id7" rel="noreferrer"><em>Specification</em></a>下:</p>
<blockquote>
<p><code>TypeError</code> will be raised if a field without a default value follows a field with a default value. This is true either when this occurs in a single class, or as a result of class inheritance.</p>
</blockquote>
<p>你在这里有一些选择来避免这个问题。</p>
<p>第一个选项是使用单独的基类将具有默认值的字段强制放到MRO顺序的后面位置。无论如何,避免直接在要用作基类的类(如<code>Parent</code>)上设置字段。</p>
<p>以下类层次结构有效:</p>
<pre><code># 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
</code></pre>
<p>通过将字段拖出到<em>separate</em>基类(具有不带默认值的字段和具有默认值的字段)中,并按照精心选择的继承顺序,可以生成一个MRO,将所有不带默认值的字段放在具有默认值的字段之前。<code>Child</code>的反向MRO(忽略<code>object</code>)是:</p>
<pre><code>_ParentBase
_ChildBase
_ParentDefaultsBase
_ChildDefaultsBase
Parent
</code></pre>
<p>请注意,<code>Parent</code>不设置任何新字段,因此在这里,它以字段列表顺序的“最后一个”结束并不重要。具有不带默认值的字段的类(<code>_ParentBase</code>和<code>_ChildBase</code>)先于具有默认值的字段的类(<code>_ParentDefaultsBase</code>和<code>_ChildDefaultsBase</code>)。</p>
<p>结果是<code>Parent</code>和<code>Child</code>类具有一个旧字段,而<code>Child</code>仍然是<code>Parent</code>的一个子类:</p>
<pre><code>>>> from inspect import signature
>>> signature(Parent)
<Signature (name: str, age: int, ugly: bool = False) -> None>
>>> signature(Child)
<Signature (name: str, age: int, school: str, ugly: bool = True) -> None>
>>> issubclass(Child, Parent)
True
</code></pre>
<p>因此您可以创建两个类的实例:</p>
<pre><code>>>> 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)
</code></pre>
<p>另一个选项是只使用具有默认值的字段;您仍然可以在错误中设置为不提供<code>school</code>值,方法是在<code>__post_init__</code>中增加一个值:</p>
<pre><code>_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'")
</code></pre>
<p>但是这个<em>确实改变了字段顺序;<code>school</code>在^{<cd3>之后结束:</p>
<pre><code><Signature (name: str, age: int, ugly: bool = True, school: str = <object object at 0x1101d1210>) -> None>
</code></pre>
<p>类型提示检查器会抱怨<code>_no_default</code>不是字符串。</p>
<p>您还可以使用<a href="http://www.attrs.org/en/stable/" rel="noreferrer">^{<cd27>} project</a>,它是激发<code>dataclasses</code>灵感的项目。它使用不同的继承合并策略;它将子类中的重写字段拉到字段列表的末尾,因此<code>Parent</code>类中的<code>['name', 'age', 'ugly']</code>变成<code>Child</code>类中的<code>['name', 'age', 'school', 'ugly']</code>;通过使用默认值重写字段,<code>attrs</code>允许重写,而无需执行MRO舞蹈。</p>
<p><code>attrs</code>支持定义不带类型提示的字段,但让我们通过设置<code>auto_attribs=True</code>来坚持<a href="http://www.attrs.org/en/stable/types.html" rel="noreferrer">supported type hinting mode</a>:</p>
<pre><code>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
</code></pre>