AI大模型入门1.3-python基础-类

视频讲解:

https://space.bilibili.com/70431433?spm_id_from=333.1007.0.0

文章和代码:

https://github.com/zyf-ngu/Qmatter

和对象

类和对象是Python编程的核心构件,类有2个核心的组件,一是变量,类中称为属性,二是函数,类中称为方法,属性就是变量,方法就是函数,只是在类里重新起了个名字。

类可以理解为更高层次的函数,把某一类对象都有的通用行为抽象出来,比如猫科动物类;对象是类的实例化,比如实例化一个猫。一个类可以通过继承的方式在保留原类的不断扩展独有的一些特点。

在Python中,类在定义时,使用class关键字,类名首字母大写。类名后可以加括号,括号可以为空。不为空的话一般是写继承的基类名。

属性

基本概念

前面介绍类的属性就是变量,那么属性有哪些种类呢?又该如何赋值呢?

属性种类:

属性包括类属性和实例属性,类属性就是所有实例都有的,在类的一级结构下,可通过类名和实例名访问,实例属性在init初始化方法里,只能通过实例名访问。实例属性又包括私有属性,前面加双下划线,只能在类的方法中访问,实例名无法访问。

类的实例也可以作为属性,当给类添加的细节越来越多:属性和方法清单以及文件都越来越长。在这种情况下,可能需要将类的一部分作为一个独立的类提取出来。然后把这个独立的类作为大类的一个属性。

属性赋值:

类属性在类定义时会赋值(默认值),类的所有实例都会共享这个初始值;

实例属性在_init__方法里赋值,self.instance_attirbute=0。 值的来源有2种 ,一是直接给定初始值,可以为0或者空字符串等;二是使用_init__括号里的形参(也就意味着实例属性可以和括号里的形参数量不一致。)但我们知道,形参只是符号,没有具体值,真正的值来自调用函数传过来的实参。

那么怎么调用__init__方法呢?

答案就是在类的实例化时,__init__方法会自动调用,因此实例化时,类名括号里的参数应该和__init__方法的形参一致(有默认值的可不用)。如果有继承,那么也需要包括父类的__init__方法的形参。

类的属性和方法的参数的比较:

|----------|-----------------------|-----------------------|
| 特征 | 属性(Attribute) | 方法参数(Parameter) |
| 定义位置 | 类内部直接定义或通过__init__初始化 | 方法定义时的括号内声明 |
| 生命周期 | 随对象存在,直到对象销毁 | 仅在方法调用期间存在 |
| 数据流向 | 存储对象状态数据 | 传递外部数据到方法内部 |
| 访问方式 | 通过self.属性名访问 | 在方法内直接使用参数名访问 |
| 修改权限 | 通常通过方法间接修改 | 作为输入数据不可直接修改(除非是可变对象) |

实例属性是在实例化时确定的,能够根据实例化时传递的参数给不同的实例赋予不同的属性值,灵活性较高;而类属性的初始值在类定义时就确定了,可以当做常量和默认值使用。

属性访问和修改:

这2种属性都可以通过实例名访问和动态修改,类属性还可以直接通过类名访问修改,使用句点表示法。实例名.属性;

类属性修改所有的实例对象访问得到的结果都会随着修改;而实例属性的话,每个实例都有属于自己的属性副本,一个实例对象修改不会影响其他实例对象的访问结果。

可以以三种不同的方式修改属性的值:

直接通过实例进行修改(不推荐,不安全);

通过方法进行设置(推荐);

通过方法进行递增(增加特定的值)。

代码实战

类属性和实例属性

class BaseClass:

类属性

class_attribute='这是类属性'

def init(self,instance_attribute):

实例属性

self.instance_attribute1=instance_attribute # 通过形参赋值,类实例化时传入

self.__instance_attribute2=0 # 可以直接初始化,不用通过形参赋值,且是私有属性,实例名和类名不能直接访问

类实例化,类名后的参数需要和__init__方法的形参一致

obj1=BaseClass(instance_attribute='instance_value1')

obj2=BaseClass(instance_attribute='instance_value2')

类属性访问,包括类名和实例名

print('类名访问类属性:',BaseClass.class_attribute)

print('实例名访问类属性:',obj1.class_attribute)

print('实例名访问类属性:',obj2.class_attribute)

实例属性访问

print('类名访问实例属性:',BaseClass.instance_attribute1) # 报错,类名不能访问实例属性

print('实例名访问实例属性:',obj1.instance_attribute1)

print('实例名访问实例属性:',obj2.instance_attribute1)

print('实例名访问实例属性:',obj2.instance_attribute2) # 报错,私有属性不能直接访问

类属性修改

BaseClass.class_attribute='类直接修改后的类属性'

print('实例名访问修改后的类属性:',obj1.class_attribute) # 输出:类直接修改后的类属性

obj1.class_attribute='实例直接修改后的类属性'

print('另一个实例名访问实例修改后的类属性:',obj2.class_attribute) # 输出:类直接修改后的类属性

实例属性修改

obj1.instance_attribute1='实例修改后的实例属性'

print('实例名访问修改后的实例属性:',obj1.instance_attribute1) # 输出:实例修改后的实例属性

print('另一个实例名访问修改后的实例属性:',obj2.instance_attribute1) # 输出:instance_value2(实例属性没变)

方法

类中的函数称为方法;有关函数的一切都适用于方法,就目前而言,唯一重要的差别是调用方法的方式,即需要在调用的方法前面加实例/对象名,以表明是哪个实例对象调用的,因为同一个方法名不同的实例可能实现的不一样。

方法种类:

方法又包括基本方法,初始化方法__init__,类方法@classmethod,静态方法@staticmethod,基本方法和初始化方法有第一个隐参数self,类方法有第一个隐参数cls,静态方法无隐参数,不需要访问实例属性。

  1. init() 初始化方法

init()是一个特殊的初始化方法,每当根据类创建新实例时,Python都会自动运行它。在这个方法的名称中,开头和末尾各有两个下划线,这是一种约定,表示类私有。

方法__init__()定义中,形参self必不可少,还必须位于其他形参的前面。为何必须在方法定义中包含形参self呢?因为Python调用这个__init__()方法来创建实例时,将自动传入实参self。每个与类相关联的方法调用都自动传递实参self,它是一个指向实例本身的引用,让实例能够访问类中的属性和方法。

2.基本方法

类中的基本方法的第一个形参都是self,可以有其它的形参,若有,则调用时需要按照普通函数那样传入对应的实参;类中调用基本方法:self.+名字。类外调用:实例名.方法名。

3.类方法

方法上面使用@classmethod装饰器表示,第一个参数为cls,使用类名.方法名进行调用,也可以通过类的实例来调用。更常见和推荐通过类名调用。

可以访问和修改类属性 (cls.class_attribute)。不能直接访问或修改特定实例的属性(因为没有 self 指向实例)。

4.静态方法

方法上面使用@staticmethod装饰器表示,参数只需要正常形参即可,使用类名.方法名进行调用。

定义独立于类和实例状态 的辅助功能,不能 直接访问或修改类属性或实例属性(因为没有 cls 或 self)。如果需要操作数据,必须通过参数显式传递。本质上就是一个放在类里面的普通函数,仅仅因为逻辑上属于这个类而放在这里。比如执行与类相关但不依赖于类或实例具体状态的辅助计算、格式化、验证等。

5.抽象方法

方法上面使用@abstractmethod,需要继承抽象类ABC。具体见下面的抽象继承。

继承

如果要编写的类是另一个现成类的特殊版本,可使用继承。类名括号内写另一个类的名字,即为继承,括号内的类为父类/基类,新建的为子类。子类自动获得父类的属性和方法,并可通过扩展或修改实现新功能。

class Parent: # 父类/基类

pass

class Child(Parent): # 子类/派生类 (Parent 写在括号中)

pass

继承的功能意义

继承就是子类默认拥有基类完整的属性和方法,然后又可以改动和新增自己的属性和方法,还可以重写父类的方法,只需要与要重写的父类方法同名。这样子类实例化对象后直接调用子类的方法而不是父类的方法,若没有重写,则仍调用父类的方法。这点在实际项目中经常遇到,子类继承父类之后,实例化对象调用的方法在子类的代码里没找到,大概率就是父类有这个方法,子类未重写。

注意一个特殊情况,即子类实例调用父类的其中一个方法A,该方法调用父类的另一个方法B,而子类重写了方法B,那么子类实例调用方法A的时候最终也会调用子类的方法B。这就是类的多态性。

class Parent:

def methodA(self):

print("Parent: methodA called")

self.methodB() # 此处实际调用子类重写的 methodB

def methodB(self):

print("Parent: methodB called")

class Child(Parent):

def methodB(self): # 重写父类的 methodB

print("Child: methodB called")

child = Child()

child.methodA() # 调用父类的 methodA 但输出 Child: methodB called # 实际调用子类重写的 methodB

继承的核心功能如下:

|------------|--------------------|-----------------------------------------------|
| 功能 | 说明 | 示例代码片段 |
| 代码复用 | 子类自动获得父类所有非私有属性和方法 | child = Child() child.parent_method() |
| 功能扩展 | 子类添加新属性和方法 | class Child(Parent): def new_method(self):... |
| 方法重写 | 子类定义同名方法覆盖父类实现 | class Child(Parent): def method(self):... |
| 多态支持 | 不同子类对同一方法有不同实现 | obj.method() 根据实际对象类型调用对应实现 |
| 接口标准化 | 通过抽象基类强制子类实现特定方法 | from abc import ABC, abstractmethod |

继承的种类

单继承 子类只继承一个父类(最基础形式)

class Animal:

def speak(self):

print('动物会发出声音')

class Dog(Animal):

def spark(self):

print('小狗会汪汪叫')

dog=Dog()

dog.speak() # 调用父类的方法, 输出:动物会发出声音

dog.spark() # 调用自己的方法,输出:小狗会汪汪叫

多继承 子类同时继承多个父类(Python 特色)

class Flyer:

def fly(self):

print("在空中飞行")

class Swimmer:

def swim(self):

print("在水里游泳")

class Duck(Flyer, Swimmer): # 多继承

def quack(self):

print("嘎嘎嘎!")

duck = Duck()

duck.fly() # 来自 Flyer -> 在空中飞行

duck.swim() # 来自 Swimmer -> 在水里游泳

duck.quack() # 子类自有方法 -> 嘎嘎嘎!

多层继承 **;**形成继承链(祖父 → 父 → 子)

class Vehicle:

def transport(self):

print("运输工具")

class Car(Vehicle):

def run(self):

print("在公路上行驶")

class ElectricCar(Car): # 多层继承

def charge(self):

print("电能驱动")

tesla = ElectricCar()

tesla.transport() # 继承自 Vehicle -> 运输工具

tesla.run() # 继承自 Car -> 在公路上行驶

tesla.charge() # 自有方法 -> 电能驱动

菱形继承(钻石问题) : 多继承中父类有共同祖先(通过 MRO 算法解决)

class A:

def show(self):

print('A')

class B(A):

def show(self):

print('B')

class C(A):

def show(self):

print('C')

class D(B,C):

def show(self):

print('D') # (1)如果子类重写方法,则直接调用自己的方法

pass # (2)如果子类不重写方法,则按照MRO顺序调用父类祖先类方法

d=D()

d.show() # 重写输出D;不重写:输出B

print('D.mro:',D.mro()) # D.mro: [<class 'main.D'>, <class 'main.B'>, <class 'main.C'>, <class 'main.A'>, <class 'object'>]

抽象基类继承 **:**强制子类实现特定方法(接口约束)

from abc import ABC, abstractmethod

class Shape(ABC): # 抽象基类

@abstractmethod

def area(self): # 抽象方法

pass

class Circle(Shape):

def init(self, radius):

self.radius = radius

def area(self): # 必须实现抽象方法

return 3.14 * self.radius ** 2

shape = Shape() # 报错:不能实例化抽象类

circle = Circle(5)

print(circle.area()) # 78.5

方法解析顺序(MRO)

MRO(Method Resolution Order)是Python中用于在多继承中确定方法调用顺序的算法。

  1. 什么是MRO? MRO是一个顺序列表,它定义了在多继承中,当子类调用一个方法时,Python解释器搜索该方法的顺序。 在Python中,每个类都有一个__mro__属性,它是一个列表,按照方法解析顺序列出了该类及其所有基类。

其核心功能如下:

解决菱形继承问题,保证子类调用方法时按照合适的顺序,保证方法调用的确定性和一致性 **,**避免父类方法被多次调用或遗漏调用(在super()的使用中体现)。

  1. MRO的原理(C3线性化算法) Python使用C3线性化算法来计算MRO。该算法遵循以下三个原则:
  • 子类优先于父类:例如,在class C(A, B)中,A和B是父类,则C的MRO中C本身在最前面。

  • 继承图中保持基类的顺序:例如,class C(A, B)中,A在B前面,那么在MRO中A的基类也会在B的基类前面(前提是不违反子类优先)。

  • 单调性:若类 X 在类 Y 前,则 X 的所有子类也在 Y 前。

算法步骤(简述):

  • 对于每个类,它的MRO是它自身加上其父类的MRO的合并。

  • 合并规则: 给定类定义 class D(B, C):计算 L(D) = D + merge(L(B), L(C), [B, C])

merge 操作

取第一个列表的头部元素

如果该元素不在其他列表的尾部(非第一个位置)

则添加到结果中并从所有列表中移除

否则,检查下一个列表的头

重复直到所有列表为空

  1. 计算示例

class A:

Pass

class B(A):

Pass

class C(A):

Pass

class D(B, C):

pass

计算过程:

L(A) = [A]

L(B) = [B] + merge(L(A)) = [B, A]

L(C) = [C] + merge(L(A)) = [C, A]

L(D) = [D] + merge(L(B), L(C), [B, C])

= [D] + merge([B,A], [C,A], [B,C])

第一步:取 B(在第一个列表头,且不在其他列表尾)

= [D, B] + merge([A], [C,A], [C])

第二步:取 C(在第二个列表头,且不在其他列表尾)

= [D, B, C] + merge([A], [A])

第三步:取 A

= [D, B, C, A]

任何类都可以通过 类名.mro() 或 类名.mro 查看 MRO 顺序:

print(D.mro())# 输出: [<class 'main.D'>, <class 'main.B'>, # <class 'main.C'>, <class 'main.A'>, # <class 'object'>]

当 MRO 无法计算时,Python 会抛出类型错误:TypeError: Cannot create a consistent method resolution order (MRO) for bases A, B

super() 函数

前面介绍MRO算法的时候讲到,它可以解决多继承中的方法调用顺序问题,事实上,MRO算法只是提供了继承关系的解析顺序,而真正按照这个顺序完成方法调用的是super()函数。

super()函数用于在子类中调用父类(超类)的方法,包括初始化方法。它会根据方法解析顺序(MRO)动态地确定要调用的父类方法,从而解决了多继承中的方法调用顺序问题

super() 的核心原理

super() 基于 MRO(Method Resolution Order,方法解析顺序) 工作:具体来说:super() 在当前类的 MRO 中查找下一个类,返回一个代理对象,通过该对象调用父类方法。

  1. 基本用法:调用父类方法

先看一个最基本的用法,在子类init方法中调用父类的init方法,

class Animal:

def init(self, name):

self.name = name

print(f"Animal initialized: {self.name}")

class Dog(Animal):

def init(self, name, breed):

super().init(name) # 使用super()调用父类构造方法

Animal.init(self,name) # 硬编码即直接使用父类名称调用,需要加个self

self.breed = breed

print(f"Dog initialized: {self.breed}")

dog = Dog("Buddy", "Golden Retriever")"""

输出:

Animal initialized: Buddy

Dog initialized: Golden Retriever

"""

同理可进行扩展,在子类的普通方法调用父类的普通方法,在子类的类方法里调用父类的类方法。但是静态方法中不能使用super()函数,因为无法绑定实例。

多继承

class Camera:

def take_photo(self):

print("Taking photo")

class Phone:

def make_call(self):

print("Making call")

class SmartPhone(Camera, Phone):

def use_features(self):

super().take_photo() # 使用super 调用 Camera 的方法

super().make_call() # 使用super调用 Phone 的方法

Camera.take_photo(self) # 直接使用父类名称调用 Camera 的方法

Phone.make_call(self) # 直接使用父类名称调用 Phone 的方法

phone = SmartPhone()

phone.use_features()

那么super()函数的优势或者说到底解决了什么问题呢?难度仅仅是不用写每个父类的名称而统一使用super().调用吗?

前面讲到super()函数是解决了菱形继承的调用顺序问题,我们先看一下不使用super()函数会出现什么问题:

  1. 菱形继承问题解决方案

class A:

def process(self):

print("Processing in A")

class B(A):

def process(self):

print("Processing in B")

super().process()

A.process(self)

class C(A):

def process(self):

print("Processing in C")

super().process()

A.process(self)

class D(B, C):

def process(self):

print("Processing in D")

super().process()

B.process(self)

C.process(self)

def process2(self):

print("Processing2 in D")

super().process()

B.process(self)

C.process(self)

d = D()

d.process2()

结合上面的代码我们来分析一下使用super()函数的作用:

(1)第一种情况,所有的类都不使用super()函数,那么第一个问题就是当类D多继承B和C的时候,需要分别写出B.process(self)和C.process(self),顺序需要我们手动写;然后运行的时候会输出:D-B-A-C-A,也就是说是按照B.process(self)和C.process(self)依次运行的,也就导致B和C分别调用了一次A;

(2)第二种情况,最后的子类D使用super()函数,其它类使用父类名称,则输出:D-B-A,可以看到,遗漏了类C的方法,这是因为D找到B之后直接使用父类名称调用了A就结束了。但这种情况适用于需要根据条件明确使用不同的父类,分条件调用父类方法。

(3)第三种情况,全部使用super()函数:则输出D-B-C-A,这说明是按照MRO提供的解析顺序调用父类方法的;

(4)第四种情况:最后的子类在不同名称的方法内部调用父类的方法,输出的顺序和上面一致。这说明什么?说明super()只是一个调用方法,可以在任意一个子类方法中使用,只需要标注要调用的父类方法。前面三种情况是为了完整介绍super()函数调用父类方法的顺序,但在实际项目中,子类继承的多个父类不可能都拥有同样名称的方法,因此并不是说super()函数只能在同名方法中使用。

(5)第五种情况,B或C不使用super()函数,也不使用父类名称调用,则会输出D-B,停止到不继续调用的类,这意味着在子类调用某个方法时,super()函数只是按照MRO算法的顺序去寻找这个方法,找到之后会执行;但并不是所有同名方法都会被执行(那子类方法重写就没有意义了),而是只有这个方法内部又调用了super()或父类名称,那么就会继续执行MRO链中下一个类的同名方法,以此类推,直到整个MRO链被遍历完或者某个方法没有调用super()而终止。

深度学习中使用 super() 的原因分析

在深度学习框架(如 PyTorch)中,super() 被广泛用于网络构建尤其实是在初始化方法里,主要原因如下:

  1. 确保父类初始化正确执行

深度学习模型通常继承自框架的基类(如 nn.Module),这些基类包含关键初始化逻辑

import torch.nn as nn

class MyModel(nn.Module):

def init(self):

super().init() # 必须调用父类初始化

self.layer = nn.Linear(10, 5)

def forward(self, x):

return self.layer(x)

不调用 super().init()的后果

模型无法注册子模块(参数不被识别)

无法正确转移到 GPU

无法保存/加载模型状态

  1. 支持模块化设计

深度学习网络常采用分层结构 ,super() 实现各层的协作:

class BaseBlock(nn.Module):

def init(self, in_channels):

super().init()

self.conv = nn.Conv2d(in_channels, in_channels, 3, padding=1)

def forward(self, x):

return self.conv(x)

class ResidualBlock(BaseBlock):

def init(self, in_channels):

super().init(in_channels) # 初始化基础卷积

def forward(self, x):

return x + super().forward(x) # 调用父类forward并添加残差连接

继承 vs 组合

|--------|---------------------|-------------------------------------------------------|
| | 继承 (is-a 关系) | 组合 (has-a 关系) |
| 关系 | "是一个" (狗是动物) | "有一个" (汽车有发动机) |
| 代码 | class Dog(Animal): | class Car: def init(self): self.engine = Engine() |
| 优点 | 代码复用性强 | 降低耦合度 |
| 缺点 | 可能产生深度耦合 | 需显式调用组件方法 |
| 原则 | 优先使用组合,继承用于真正"是"的关系 | 灵活构建复杂系统 |

总结:Python 继承要点

最佳实践:避免深度继承链(建议 ≤3 层)

多继承时使用 Mixin 类(单一功能的小类)

优先组合而非继承降低耦合度

相关推荐
UR的出不克2 小时前
Python实现SMZDM数据处理系统:从爬虫到数据分析的完整实践
爬虫·python·数据分析
jimmyleeee2 小时前
人工智能基础知识笔记三十四:提升RAG效果的几种技术
人工智能·笔记
智能相对论2 小时前
【年度AI观察】2026,车企反攻智能硬件
人工智能·智能硬件
一代土怪2 小时前
django中实时更新数据库
python·django
m0_466525292 小时前
AI医疗的东软答卷:从技术破局到产业融合
人工智能
学习3人组2 小时前
AI视觉Python方向专业技术名词
开发语言·人工智能·python
静听松涛1332 小时前
通用人工智能(AGI)的阶段性定义与里程碑
人工智能
落雨盛夏2 小时前
深度学习|李哥1
人工智能·深度学习
Blossom.1182 小时前
大模型分布式训练通信优化:从Ring All-Reduce到分层压缩的实战演进
人工智能·分布式·python·深度学习·神经网络·机器学习·迁移学习