Python中的数据类继承

3.7及以后的版本在Python中引入了数据类继承。这篇文章大致解释了多级继承和如何使用Python中的数据类继承。

Python中的继承

Python中的数据类继承用于从其父类中获取子类中的数据,这有助于减少重复的代码,使代码可重复使用。

让我们看一个继承的例子:

该程序导入了dataclass 库包,允许创建装饰类。这里创建的第一个类是Parent ,它有两个成员方法–字符串name 和整数age

然后在这里创建了一个Parent 的子类。Child 类引入了一个新的成员方法 –school

Parent 类创建了一个实例对象jack ,它向该类传递两个参数。另一个实例对象,jack_son ,是为Child 类创建的。

由于Child 类是Parent 的一个子类,数据成员可以在Child 类中派生。这就是Python中数据类继承的主要特征。

from dataclasses import dataclass
@dataclass
class Parent:
    name: str
    age: int
    def print_name(self):
        print(f"Name is '{self.name}' and age is= {self.age}")
@dataclass
class Child(Parent):
    school: str
jack = Parent('Jack snr', 35)
jack_son = Child('Jack jnr', 12, school='havard')
jack_son.print_name()

输出:

C:python38python.exe "C:/Users/Win 10/PycharmProjects/class inheritance/2.py"
Name is 'Jack jnr' and age is= 12
Process finished with exit code 0

Python中的多级继承

我们已经看到了Python中的数据类继承是如何工作的,现在我们将看一下多级继承的概念。这是一种继承类型,从父类中创建的子类被用作后续孙类的父类。

下面的例子以一种简单的形式演示了多级继承:

父类

创建一个类Parent ,有一个构造函数__init__ ,和一个成员方法–print_method 。构造函数打印了语句"Initialized in Parent" ,这样当该类被其子类调用时就会显示出来。

print_method 函数有一个参数b ,当这个方法被调用时,它将被打印出来。

子类

Child 类是由Parent 派生的,并在其构造函数中打印了一个语句。super().__init__ 指的是基类与子类。

我们使用super() ,这样子类所使用的任何潜在的合作性多继承将在方法解析顺序(MRO)中调用适当的下一个父类函数。

成员方法print_method 是重载的,一个打印语句打印了b 的值。在这里,super() 也是指其父类的成员方法。

孙子类

在这一点上,程序只是用模板(重复编码)的结构来创建另一个从Child 类中继承的类。在print_method 内,b 的值通过super() 进行递增。

主函数

最后,创建了main 函数,它创建了一个对象ob ,并成为GrandChild() 的实例。最后,对象ob 调用print_method

这就是在Python中使用数据类继承时,多级类是如何堆叠的。

class Parent:
    def __init__(self):
        print("Initialized in Parent")
    def print_method(self, b):
        print("Printing from class Parent:", b)
class Child(Parent):
    def __init__(self):
        print("Initialized in Child")
        super().__init__()
    def print_method(self, b):
        print("Printing from class Child:", b)
        super().print_method(b + 1)
class GrandChild(Child):
    def __init__(self):
        print("Initialized in Grand Child")
        super().__init__()
    def print_method(self, b):
        print("Printing from class Grand Child:", b)
        super().print_method(b + 1)
if __name__ == '__main__':
    ob = GrandChild()
    ob.print_method(10)

输出:

C:python38python.exe "C:/Users/Win 10/PycharmProjects/class inheritance/3.py"
Initialized in Grand Child
Initialized in Child
Initialized in Parent
Printing from class Grand Child: 10
Printing from class Child: 11
Printing from class Parent: 12
Process finished with exit code 0

让我们了解一下这里的代码是怎么做的:

main 函数将值10 传递给类Grand Childprint_method 函数 。按照MRO(方法解析顺序),程序首先执行类Grand Child ,打印出__init__ 语句,然后转到它的父类–Child 类。

按照MRO,Child 类和Parent 类打印它们的__init__ 语句,然后编译器跟随回到GrandChild 类的print_method 。这个方法打印10b 的值),然后用super() 来增加其超类–Child 类中的b 的值。

然后编译器转到Child 类的print_method ,并打印出11 。然后,在MRO的最后一层是Parent 类,打印出12

由于在Parent 类之上不存在超类,所以程序退出。

我们已经了解了Python中数据类继承的多级继承是如何工作的,下一节将介绍从父类继承属性的概念以及如何修改它。

使用 Python 中的数据类继承在基类和子类之间混合默认和非默认属性

我们已经看到了在Python的数据类继承中如何使用子类来访问其父类的数据成员,以及多级继承如何工作。现在,一个问题出现了,如果一个子类可以访问它的超类的数据成员,它可以对它进行修改吗?

答案是肯定的,但它必须避免出现类型错误。例如,在下面的程序中,有两个类,一个是Parent 类,一个是子类Child

FalseParent 有三个数据成员 –name,age, 和一个默认设置为ugly 的bool变量。三个成员方法打印name,age, 和id。

现在,在派生于Parent 的装饰的Child 类中,引入了一个新的数据成员–school 。通过它,该类将变量ugly 的属性从False 改为True

两个对象,jack 用于Parentjack_son 用于Child ,被创建。这些对象向它们的类传递参数,两个对象都调用了print_id 方法,并打印出细节。

使用这个方法来改变基类的默认值的一个主要问题是它会导致一个TypeError。

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"ID: Name - {self.name}, age = {self.age}")
@dataclass
class Child(Parent):
    school: str
    ugly: bool = True
jack = Parent('jack snr', 32, ugly=True)
jack_son = Child('Mathew', 14, school = 'cambridge', ugly=True)
jack.print_id()
jack_son.print_id()

输出:

    raise TypeError(f'non-default argument {f.name!r} '
TypeError: non-default argument 'school' follows default argument

这个错误背后的原因是,在Python的数据类继承中,由于数据类混合属性的方式,属性不能在基类中使用默认值,然后在子类中使用无默认值(位置属性)。

这是因为属性从MRO的底部开始合并,并按照第一眼看到的顺序建立一个有序的属性列表,重写仍然在原来的位置。

ugly 为默认值,Parentname,age, 和ugly 开始,然后Child 在该列表的末尾添加school (列表中已有ugly )。

这导致在列表中出现了name,age,ugly, 和school ,由于school 没有默认值,__init__ 函数将结果列为错误的参数。

@dataclass 装饰器创建一个新的数据类时,它以反向MRO搜索该类的所有基类(从对象开始),并将每个基类的字段添加到它找到的每个数据类的有序字段映射中。

然后,在所有基类的字段被添加到有序映射中后,它再将其字段添加到有序映射中。这个合并计算的有序字段映射将被所有创建的方法使用。

由于字段的安排,派生类取代了基类。

如果一个没有默认值的字段跟在一个有默认值的字段后面,将会产生一个TypeError。无论它是发生在单个类中还是由于类的继承,都是如此。

绕过这个问题的第一个选择是使用不同的基类将有缺省值的字段强制放到MRO顺序中的后面位置。不惜一切代价避免直接在将被用作基类的类上设置字段,如Parent ,。

这个程序有带字段的基类,没有默认值的字段被分开。公共类派生自base-withbase-without 类。

公有类的子类将基类放在前面。

from dataclasses import dataclass
@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
@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"ID: Name - {self.name}, age = {self.age}")
@dataclass
class Child(_ChildDefaultsBase, Parent, _ChildBase):
    pass
Amit = Parent('Amit snr', 32, ugly=True)
Amit_son = Child('Amit jnr', 12, school='iit', ugly=True)
Amit.print_id()
Amit_son.print_id()

输出:

C:python38python.exe "C:/main.py"
The Name is Amit snr and Amit snr is 32 year old
The Name is Amit jnr and Amit jnr is 12 year old
Process finished with exit code 0

这里创建的MRO将无缺省的字段优先于有缺省的字段,方法是将字段拆分到有字段的独立基类without defaultswith defaults ,并仔细选择继承顺序。Child‘的MRO是:

<class 'object'>
        ||
<class '__main__._ParentBase'>,
        ||
<class '__main__._ChildBase'>
        ||
<class '__main__._ParentDefaultsBase'>,
        ||
<class '__main__.Parent'>,
        ||
<class '__main__._ChildDefaultsBase'>,
        ||
<class '__main__._Child'>

尽管Parent 没有创建任何新的字段,但它继承了来自ParentDefaultsBase 的字段,不应该在字段列表顺序中排在最后。所以,ChildDefaultsBase 被保留在最后,以满足正确的顺序类型。

数据类的规则也得到了满足,因为ParentBaseChildBase 这两个拥有无缺省字段的类排在ParentDefaultsBaseChildDefaultsBase 之前,这两个类拥有有缺省字段。

因此,Child 仍然是Parent 的一个子类,而ParentChild 类有一个正确的字段顺序:

__ Program Above __
print(signature(Parent))
print(signature(Child))

输出:

Python中的数据类继承

结论

这篇文章详细解释了 Python 中的数据类继承。像数据类、子类、多级继承和从基类到子类的混合属性这样的概念被彻底解释了。