当前位置:首页>python>Python学习第三课(第1节:类的继承与类装饰器)

Python学习第三课(第1节:类的继承与类装饰器)

  • 2026-10-21 13:29:02
Python学习第三课(第1节:类的继承与类装饰器)

Python 中类的继承与类装饰器

在上一节的课程中,我们已经初步探索了 Python 中的类,大概了解了类的使用方法。但还有一些需要进一步讲解的内容,那就是类的继承和类装饰器,尤其是类的继承,俗话说的好——“Python不知类继承,便称盖茨也枉然”!

一、什么是类的继承?

“继承”这两个字应该很容易理解,听得最多的就是子女从父母那边继承了财产(当然,也可能是债务),没有财产继承的也会一定程度上继承父母的颜值、身高和性格等基因所决定的先天性因素。

Python 中的类也有这样的特点,可以继承另一个类的所有非私有属性!

说到这里,需要补充一点上节课遗漏的知识点——私有属性。私有属性就是带双下划线(__)的属性,比如:self.__name__ = 'CMOS 三好先生' 中的 __name__ 属性就是私有属性。

言归正传!接着讲类的继承。

比如,我们有一个动物类,有 name 和 age 实例属性和 eat 实例方法(下文若无特殊说明,属性、方法均指实例的属性和方法):

class Animal:    def __init__(self, name, age):        self.name = name        self.age = age    def eat(self):        print(f"{self.name} is eating.")

我在前一节课说过人、猫、狗和兔子都是动物类,因为它们有一定的共性,比如有名字、年龄,会吃东西;也有一定的差异,比如人会说话会模仿动物叫,猫叫是“喵喵喵”,狗吠是“汪汪汪”,兔子则不会叫。

那么,我们要如何定义人、猫、狗和兔子的类呢?按照 Animal 类重新写一遍?这时候“继承”就需要派上用场了!

下面我就以猫、狗作为例子(至于人嘛,就别来凑热闹了)来看看如何继承 Animal 类:
class Cat(Animal):    def __init__(self, name, age, breed):        super().__init__(name, age)        self.breed = breed    def meow(self):        print(f"{self.name} is meowing.")class Dog(Animal):    def __init__(self, name, age, breed):        super().__init__(name, age)        self.breed = breed    def bark(self):        print(f"{self.name} is barking.")# 子类用法if __name__ == "__main__":    persian = Cat("Tom", 5, "Persian")    persian.eat()    persian.meow()    labrador = Dog("Rufus", 3, "Labrador")    labrador.eat()    labrador.bark()

从上述的代码可以看出,猫和狗的 class 定义时,都加上了“(Animal)”,这个括号内的类名就表示继承的父类,也就是说 Animal 是 Cat 和 Dog 类的父类,Cat 和 Dog 类是 Animal 类的子类。因为都是继承于 Animal 类,所以我们也把 Cat 和 Dog 类叫做兄弟子类。

因为子类拥有父类的所有非私有属性,所以 Cat 和 Dog 都有 name、age 和 eat 属性,我们就不用再重复写这些代码。

但是,你们会发现在 __init__ 方法中,我们新增了一行 super().__init__(name, age),还增加了一个 breed 参数,然后赋值给了子类的 breed 属性。

先说 breed 属性,这是 Cat 和 Dog 类新增的属性,用来表示猫和狗的品种,这个就不用多解释了。

再来说 super() ,这是类中的内置函数。在子类中,super() 按照继承顺序调用“下一个父类”的方法。通常用来调用被重写的父类方法(比如 __init__ 方法)。

super() 返回的是一个“代理对象”,通过这个对象可以访问父类(或多重继承链上的其他类)中的方法和属性,而不需要写父类的类名。说到这里,就引出了一个新的概念——多重继承,我们后面会详细介绍。

再来看看两个子类里新增的方法:Cat 类里的 meow() 和 Dog 类里的 bark()。这就是定义了该子类特有的方法,其父类和其他兄弟子类的实例都不能调用。

讲到这里,单继承的逻辑就讲完了,接下来我们来学习多重继承。


二、什么是多重继承?

顾名思义,“多重继承”就表示有多个继承的父对象。可以这样理解,一个子类可以继承多个类的属性。比如猫和狗,除了是动物之外,也可以是人类的宠物。我们就可以写下面这样的代码(以 Cat 类为例):

class Pet:    def __init__(self, breed, owner, nickname):        self.breed = breed        self.owner = owner        self.nickname = nickname    def play(self):        print(f"{self.nickname} is playing with {self.owner}.")class PetCat(Cat, Pet):    def __init__(self, name, age, breed, owner, nickname):        super().__init__(name, age, breed)        Pet.__init__(self, breed, owner, nickname)    def info(self):        print(f"Name: {self.name}, Age: {self.age}, Breed: "                + f"{self.breed}, Owner: {self.owner}, Nickname: {self.nickname}")if __name__ == '__main__':    pet_persian = PetCat("Tom", 5, "Persian", "John", "Tommy")    pet_persian.eat()    pet_persian.meow()    pet_persian.play()    pet_persian.info()

Pet 类是新增的一个表示宠物的类,有breed、owenr、nickname 和 play 属性,PetCat 类是表示宠物猫的类,继承了 Cat 和 Pet 类。通过最后的初始化和调用代码可以看出,PetCat 继承了 Animal、Cat 和 Pet 类的属性。

需要多说几句,PetCat 类的继承顺序很重要,如果写成“PetCat(Pet, Cat)”,运行后续的代码就会报错:

Traceback (most recent call last):  File "lesson3_2.py", line 56, in <module>    pet_persian.eat()  File "lesson3_2.py", line 7, in eat    print(f"{self.name} is eating.")AttributeError: 'PetCat' object has no attribute 'name'

因为后面 __init__ 方法里的 super(PetCat, self).__init__ 里的 super 方法会返回多重继承的第一个父类 Pet 的代理对象,然后下面一行 Pet.__init__ 又进行了一次初始化,于是相当于用 Pet.__init__ 初始化了两次,没有调用到 Cat 类的 __init__ 方法,最后在调用 pet_persian.eat() 时就会报错了。

所以,如果在多重继承时,使用 super() 继承初始化方法必须传入第一个继承父类的 __init__ 参数,其他父类的则需要显示调用 __init__ 方法(比如:XXX.__init__(...))。

当然,也可以都用显示调用的方式对子类进行初始化,这样就不需要考虑继承的类的顺序了。比如:

class PetCat(Pet, Cat):    def __init__(self, name, age, breed, owner, nickname):        Cat.__init__(self, name, age, breed)        Pet.__init__(self, breed, owner, nickname)  # 等效于 super().__init__(...)

需要注意 super().__init__ 和 <ParentClass>.__init__ 的参数区别,前者不需要传入 self 参数。

至于多重继承的父类顺序的问题,这个是一个叫做 MRO(Method Resolution Order,方法解析顺序)的机制决定的。

MRO 是确定多重继承中方法和属性查找顺序的规则,它通过C3线性化算法(C3 Linearization)生成一个有序列表,保证了查找的一致性、局部优先和单调性,防止了“菱形继承(钻石继承)”等复杂情况下的歧义,该算法在Python 3及新式类中是标准实现,可通过__mro__或mro()方法查看。
print(PetCat.mro())
结果如下:

[<class'__main__.PetCat'>, <class'__main__.Cat'>, <class'__main__.Animal'>, <class'__main__.Pet'>, <class'object'>]

关于 MRO 的详细讲解,这里就不继续展开了,可以简单理解为:多重继承时,多个父类,如果有相同的属性,那么先继承的父类里的属性优先继承。

另外,在多重继承中,super() 保证了公共父类只被初始化一次。比如:

class Base:    def __init__(s):        print('Base init.')class A(Base):    def __init__(s):        print('A init.')        super().__init__()        print('A init end.')class B(Base):    def __init__(s):        print('B init.')        super().__init__()        print('B init end.')class C(A, B):    def __init__(s):        print('C init.')        super().__init__()        print('C init end.')C()

运行结果如下:

可以看到“Base init.”只显示了一次,说明 Base 类的 __init__ 方法只调用了一次。如果把 C 类中的 super().__init__() 修改为父类显示调用__init__,如下:

class C(A, B):    def __init__(s):        print('C init.')        A.__init__(s)        B.__init__(s)        print('C init end.')

运行结果如下:

你会发现,不仅 Base.__init__ 调用了两次,A.__init__(s) 调用时,竟然还调用了 B.__init__(s) !!!

可见多重继承时,显示调用 __init__ 有多么浪费资源了吧?所以,多重继承的父类参数相同时,使用 super() 可以大大降低资源消耗。

如果父类的参数不同呢?那就只能像上面的 PetCat 那样对第一个父类使用 super() 调用,其余的就只能显示调用了。所以,要记得把初始化最耗资源的父类作为第一个继承的父类。

最后,还需要对 super() 再进行一个隐藏知识点的说明,仍然以上面的 ABC 类为例。

此时的 MRO 为:

C->A->B->Base->object(Python 中的顶级对象)
  • C 类 __init__ 方法中的 super().__init__() 等价于 super(C, s).__init__()。
  • super(C, s).__init__()会依次调用 A.__init__(s) 、B.__init__(s) 和 Base.__init__(s)。
  • super(A, s).__init__() 会依次调用 B.__init__(s) 和 Base.__init__(s)。
  • super(B, s).__init__() 则只会调用 Base.__init__(s)。

也就是说,super() 的第一个参数,表示 MRO 列表中的起点,起点的类不会调用初始化方法(可以课后自己尝试一下)。

讲到这里,关于类的继承这档子事儿基本上就已经讲完了。接下来,我们再来看看类装饰器。


三、装饰的艺术 —— 类装饰器

1、类属性装饰器

类属性装饰器是一种特殊的装饰器,用于定义类的属性访问器 getter、修改器 setter 和删除器 deleter。Python内置的property函数是实现类属性装饰器的常用方法。下面是一个示例:

class Student:    def __init__(self):        self._score = 0    @property    def score(self):        return self._scorestudent = Student()print(student.score)student.score = 60  # 报错:AttributeError: can't set attribute

类属性装饰器可以限制类属性的访问权限以及合法性校验。比如上面的例子就是给 Student 类设置了一个只读属性 score,但没有设置修改器 setter,修改属性时就会报错。

下面,我们给 score 属性设置一个修改器 @score.setter:

class Student:    def __init__(self):        self._score = 0    @property    def score(self):        return self._score    @score.setter    def score(self, value):        self._score = valuestudent = Student()print(student.score)student.score = 60print(student.score)  # 结果:60

这样就不会报错了!如果你想对 score 属性进行合法值校验,就可以在 setter 代码中增加相应的判断,比如:

    @score.setter    def score(self, value):        if not (0 <= value <= 100):            raise ValueError("分数必须在 0-100 之间")        self._score = value

此时赋值 student.score = 101就会抛出异常,从而保证 score 属性的值不会超过100。

除了类属性装饰器 property 之外,还有哪些类装饰器呢?还有两个分别是:classmethod 和 staticmethod。这两个名字一看就很好理解,一个是类方法,一个是静态方法。那么,它们有什么用处呢?

别急,我们先来看看两者的使用方法:

class X(object):    x = 1    def func1(self):          print ('func1')     @classmethod    def func2(c):        print ('func2')        print (c.x)        c().func1()   # 调用 foo 方法    @staticmethod    def func3():        print ('func3')X.func2()  # classmethod 不需要实例化X.func3()  # staticmethod 也不需要实例化

可以看出,classmethod 装饰的方法 func2,可以接受一个参数 c(建议写作 cls 更符合 Python 编程约定),这个参数就是类 X 本身,可以用 print(id(c) == id(X)) 来比较两者的 id(内存地址)是否相同。类方法可以访问和修改类的属性状态,通常用于定义那些影响整个类而非某个实例的功能行为。

而 staticmethod 装饰的方法 func3,我们可以认为是类的静态方法,可以直接通过类来调用。一般来说,静态方法用于实现与类的具体实例无关的功能,其功能行为类似于普通函数,仅仅在逻辑上属于类的一种方法属性。‌

总之,类方法、静态方法再加上实例方法,正确使用这三种类型的方法,可以使代码逻辑更加清晰、结构组织更加良好,让开发者易于理解,从而有效地支持软件开发的面向对象编程范式(这个词经常应该会听说,可以理解为是一种公认的模式、理论或框架体系)。

所以,想要学好 Python 中的类,不仅仅是掌握语法就够了,更多的是要对类的设计理念和运行机制有深刻的理解。希望学完类的这2节课的内容,大家能从“写普通代码”进阶到“用类设计代码”。


好了,今天的课就讲到这里,内容和代码有点多,希望大家能够看懂并掌握相关知识点(一定要实战操作)。我们下一节课的内容是Python 中的迭代器和生成器。同学们,下课!

对了,对本节课的内容有任何疑问,欢迎在留言区给我留言哦~

最新文章

随机文章