当前位置: 首页 > 面试题库 >

Python 3.7数据类中的类继承

幸经艺
2023-03-14
问题内容

我目前正在尝试Python3.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

我该如何解决?


问题答案:

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

这是因为通过从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会将所有没有默认值的字段放在具有默认值的字段之前。的反向MRO(忽略object)为Child

_ParentBase
_ChildBase
_ParentDefaultsBase
_ChildDefaultsBase
Parent

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

结果是ParentChild具有更老实字段Child的类,而仍然是的子类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

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

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

<Signature (name: str, age: int, ugly: bool = True, school: str = <object object at 0x1101d1210>) -> 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


 类似资料:
  • 默认任何类都是基础继承自Any(与java中的Object类似),但是我们可以继承其它类。所有的类默认都是不可继承的(final),所以我们只能继承那些明确声明open或者abstract的类: open class Animal(name: String) class Person(name: String, surname: String) : Animal(name) 当我们只有单个构造器时

  • 我一直在做一个基本的类继承练习,尽管我认为我已经掌握了它的jist,但我的程序并没有按应有的方式工作。我遇到了编译错误,但还没有弄清楚原因——如果你们都能在这里帮助我,那就太好了。 首先,这里有三个文件1。人java——基类2。大学生java——一个派生的Person类。java 3。家庭java——不太确定,我认为它是自己的基类 人java有两个实例变量,String name和int age,

  • 本文向大家介绍JavaScript中的继承之类继承,包括了JavaScript中的继承之类继承的使用技巧和注意事项,需要的朋友参考一下 继承简介       在JS中继承是一个非常复杂的话题,比其他任何面向对象语言中的继承都复杂得多。在大多数其他面向对象语言中,继承一个类只需使用一个关键字即可。在JS中想要达到继承公用成员的目的,需要采取一系列措施。JS属于原型式继承,得益于这种灵活性,我们既可以

  • 1、定义类的继承 说到继承,你一定会联想到继承你老爸的家产之类的。 类的继承也是一样。 比如有一个旧类,是可以算平均数的。然后这时候有一个新类,也要用到算平均数,那么这时候我们就可以使用继承的方式。新类继承旧类,这样子新类也就有这个功能了。 通常情况下,我们叫旧类为父类,新类为子类。 首先我们来看下类的继承的基本语法: class ClassName(BaseClassName): <st

  • 问题内容: 是否有没有不将Object继承为SuperClass的类,或者可能已经过时/不赞成使用的类? 问题答案: 根据Java Object超类,不扩展。 除此之外,所有类,即 如果不扩展任何其他超类,则隐式扩展Object类。 另一方面,接口不扩展对象,因为接口根据定义不能扩展类。同样,接口不能包含可调用方法,也不能从它们实例化对象。最终实现接口时,实现类将必须扩展Object(并且不,不实

  • 我试图写一个类(SalaryEmployee)和子类(ComissionEmployee),但我没有得到正确的工资,我不能做SE1.SetSalesMount(20000)。我哪里出了问题? 这是主要的: