ICode9

精准搜索请尝试: 精确搜索
首页 > 编程语言> 文章详细

Python 3.7数据类中的类继承

2019-09-30 14:57:08  阅读:280  来源: 互联网

标签:python python-3-x python-3-7 python-dataclasses


我目前正在尝试使用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

我该如何解决?

解决方法:

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

那是因为属性是从MRO的底部开始组合的,并按照首先看到的顺序构建属性的有序列表;覆盖保留在原始位置.因此,Parent以[‘name’,’age’,’ugly’]开头,丑陋有默认值,然后Child将[‘school’]添加到该列表的末尾(丑陋已经在列表中).这意味着你最终会得到[‘name’,’age’,’ugly’,’school’],并且因为学校没有默认值,这会导致__init__的参数列表无效.

这在PEP-557 Dataclasses中记录于PEP-557 Dataclasses

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.

并在Specification下:

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.

你有几个选项可以避免这个问题.

第一个选项是使用单独的基类将具有默认值的字段强制到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,在没有默认值的情况下将所有字段置于默认值之前. Child的反向MRO(忽略对象)是:

_ParentBase
_ChildBase
_ParentDefaultsBase
_ChildDefaultsBase
Parent

请注意,Parent不会设置任何新字段,因此它在字段列表顺序中以“last”结尾并不重要.具有无默认值的字段(_ParentBase和_ChildBase)的类位于具有默认字段(_ParentDefaultsBase和_ChildDefaultsBase)的类之前.

结果是父类和子类具有较早的理智字段,而Child仍然是父类的子类:

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

另一种选择是仅使用具有默认值的字段;你仍然可以通过在__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'")

但这确实改变了野外秩序;学校在丑陋之后结束:

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

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

您还可以使用attrs project,这是启发数据类的项目.它使用不同的继承合并策略;它将子类中的重写字段拉到字段列表的末尾,因此Parent类中的[‘name’,’age’,’ugly’]变为[‘name’,’age’,’school’,’ugly’ ]在儿童班;通过使用默认值覆盖该字段,attrs允许覆盖而无需进行MRO舞蹈.

attrs支持定义没有类型提示的字段,但是让我们通过设置auto_attribs = True来坚持supported type hinting mode

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,python-3-x,python-3-7,python-dataclasses
来源: https://codeday.me/bug/20190930/1835980.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有