Python 3.7数据类中的类继承 [英] Class inheritance in Python 3.7 dataclasses

查看:112
本文介绍了Python 3.7数据类中的类继承的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我目前正在尝试Python 3.7中引入的新数据类构造.我目前坚持尝试做一些父类的继承.看来参数的顺序已被我当前的方法所破坏,使得子类中的bool参数在其他参数之前传递.这会导致类型错误.

I'm currently trying my hands on the new dataclass constructions introduced in Python 3.7. I am currently stuck on trying to do some inheritance of a parent class. It looks like the order of the arguments are botched by my current approach such that the bool parameter in the child class is passed before the other parameters. This is causing a type error.

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:

When I run this code I get this TypeError:

TypeError: non-default argument 'school' follows default argument

我该如何解决?

推荐答案

数据类组合属性的方式使您无法在基类中使用具有默认值的属性,然后在子类中使用没有默认值的属性(位置属性)

The way dataclasses combines attributes prevents you from being able to use attributes with defaults in a base class and then use attributes without a default (positional attributes) in a subclass.

这是因为通过从MRO的底部开始并按先见顺序建立属性的有序列表来组合属性.替代项将保留在其原始位置.因此,Parent['name', 'age', 'ugly']开始,其中ugly具有默认值,然后Child['school']添加到该列表的末尾(列表中已经有ugly).这意味着您以['name', 'age', 'ugly', 'school']结尾,并且由于school没有默认值,因此会导致__init__的参数列表无效.

That's because the attributes are combined by starting from the bottom of the MRO, and building up an ordered list of the attributes in first-seen order; overrides are kept in their original location. So Parent starts out with ['name', 'age', 'ugly'], where ugly has a default, and then Child adds ['school'] to the end of that list (with ugly already in the list). This means you end up with ['name', 'age', 'ugly', 'school'] and because school doesn't have a default, this results in an invalid argument listing for __init__.

此文档记录在 PEP-557 数据类 ,位于 继承 下:

@dataclass装饰器创建数据类时,它将以反向MRO(即,从object开始)遍历该类的所有基类,并为找到的每个数据类添加从该基类到字段的有序映射的字段.添加所有基类字段后,它将自己的字段添加到有序映射中.所有生成的方法都将使用此组合的,经过计算的字段有序映射.由于字段按插入顺序排列,因此派生类将覆盖基类.

When the Data Class is being created by the @dataclass decorator, it looks through all of the class's base classes in reverse MRO (that is, starting at object) 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.

并在 规范 下:

如果没有默认值的字段跟随具有默认值的字段,则会引发

TypeError.当发生在单个类中或作为类继承的结果时,都是如此.

TypeError 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.

您在这里确实有一些选择可以避免此问题.

You do have a few options here to avoid this issue.

第一种选择是使用单独的基类将具有默认值的字段强制置于MRO顺序的更高位置.不惜一切代价,避免直接在要用作基类的类上设置字段,例如Parent.

The first option is to use separate base classes to force fields with defaults into a later position in the MRO order. At all cost, avoid setting fields directly on classes that are to be used as base classes, such as 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,将所有没有默认值的字段放在具有默认值的字段之前. Child的反向MRO(忽略object)为:

By pulling out fields into separate base classes with fields without defaults and fields with defaults, and a carefully selected inheritance order, you can produce an MRO that puts all fields without defaults before those with defaults. The reversed MRO (ignoring object) for Child is:

_ParentBase
_ChildBase
_ParentDefaultsBase
_ChildDefaultsBase
Parent

请注意,Parent没有设置任何新字段,因此此处以字段列出顺序中的最后"结束并不重要.带有没有默认值(_ParentBase_ChildBase)的字段的类优先于带有默认值(_ParentDefaultsBase_ChildDefaultsBase)的字段的类.

Note that Parent doesn't set any new fields, so it doesn't matter here that it ends up 'last' in the field listing order. The classes with fields without defaults (_ParentBase and _ChildBase) precede the classes with fields with defaults (_ParentDefaultsBase and _ChildDefaultsBase).

结果是ParentChild类具有更老的字段,而Child仍然是Parent的子类:

The result is Parent and Child classes with a sane field older, while Child is still a subclass of Parent:

>>> 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

,因此您可以创建两个类的实例:

and so you can create instances of both classes:

>>> 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)

另一种选择是只使用具有默认值的字段.您仍然可以通过在__post_init__中加1来使不提供school值的错误:

Another option is to only use fields with defaults; you can still make in an error to not supply a school value, by raising one in __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'")

但是,这确实会更改字段顺序; schoolugly之后结束:

but this does alter the field order; school ends up after ugly:

<Signature (name: str, age: int, ugly: bool = True, school: str = <object object at 0x1101d1210>) -> None>

和类型提示检查器 会抱怨_no_default不是字符串.

and a type hint checker will complain about _no_default not being a string.

您还可以使用 attrs项目,它是启发dataclasses.它使用了不同的继承合并策略.它将子类中的重写字段拉到字段列表的末尾,因此Parent类中的['name', 'age', 'ugly']成为Child类中的['name', 'age', 'school', 'ugly'];通过使用默认值覆盖该字段,attrs允许覆盖而无需执行MRO跳舞.

You can also use the attrs project, which was the project that inspired dataclasses. It uses a different inheritance merging strategy; it pulls overridden fields in a subclass to the end of the fields list, so ['name', 'age', 'ugly'] in the Parent class becomes ['name', 'age', 'school', 'ugly'] in the Child class; by overriding the field with a default, attrs allows the override without needing to do a MRO dance.

attrs支持定义不带类型提示的字段,但是请遵循受支持的类型提示模式,方法是设置auto_attribs=True:

attrs supports defining fields without type hints, but lets stick to the supported type hinting mode by setting 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

这篇关于Python 3.7数据类中的类继承的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆