Class def super三者用法反思1
前情提要:这是我在学过程中做的笔记,思维可能会有一些跳跃和发散,原本我的笔记往往是记在onenote里的,但是我渐渐发现,记在它里面,如果我想在别的设备上查阅,我就还得想共享和同步,所以还是记在csdn上吧,也算是赛博备忘录吧。
本来,因为怕里面有错误,再加上我写的时候也是想到哪里就写到哪里,逻辑可能有一些混乱怕影响到大佬们,所以小弟是想将其设置仅自己可见。但我发现我不会,而且想了想。反正是小白,无所谓了。
如果有不足,还望各位大佬可以不吝赐教,小弟在此感激不敬!!!

这段代码在神经网络的模型的网络设计中十分常见,其实对于class,我只知道在这段代码中,是定义一个新类,名叫Module,但是我不太明白(nn.Module)这是什么意思,我只知道这是要这个类继承来自Pytorch的nn.Module类,即继承父类(nn.Module)的属性和方法。但是详细的就不太明白了,用的也一直是糊里糊涂地。
所以做一下总结(为了省事,我直接问了AI):
一、class的前置知识,也是对我之前学习的一个复习
类 = 蓝图 / 模板
- 类是用来描述具有相同的属性和方法的对象的集合。
- 它定义了该集合中每个对象应该具有哪些共有的属性(数据)和方法(行为)
- 对象是根据类创建的具体实例
根据我的理解这句话我认为可以重新这样写一下:类是用来描述具有相同的属性和方法的类的实例的集合。它定义了该集合中每个类的实例所共有的属性和方法。(即这里我将“对象”用“类的实例”来代替)ps:这里我将这句话提前了,便于阅读,原因和我的探究历程我放在了下面
下方是一个例子,
其中,涉及到一个点:实例化——创建一个类的实例,类的具体对象。
class Dog:
def __init__(self, name, age):
self.name = name # 属性
self.age = age # 属性
def bark(self): # 方法
return f"{self.name} says woof!"
def get_info(self):
return f"{self.name} is {self.age} years old"
# 创建对象(实例化)
my_dog = Dog("Buddy", 3)
your_dog = Dog("Max", 5)
print(my_dog.bark()) #输出为: Buddy says woof!
print(your_dog.get_info()) #输出为: Max is 5 years old
这里我在学的时候,当时用的是菜鸟教程,后面我再回顾这个问题,对于类、类的实例、类的对象有什么区别,我感到有一点绕,对于二者的定义不是很清晰,所专门又进行了了解。这里,通义千问对其进行了我认为一个非常形象的比喻

同时其还提供了一段代码实例:
class Pizza: # 这就是"菜谱"
def __init__(self, size, toppings):
self.size = size # 菜谱里写的"尺寸"
self.toppings = toppings # 菜谱里写的"配料"
def bake(self): # 菜谱里的"做法"
return f"正在烤制{self.size}寸的披萨"
# 创建实例(做菜)
my_pizza = Pizza("大", ["芝士", "香肠"]) # 做了一盘披萨
your_pizza = Pizza("小", ["菠萝", "火腿"]) # 又做了一盘披萨
那为什么说:对象是类的实例?其实这里是我自己陷入了误区,他其实就像:你根据菜谱做了第一盘披萨 → 第一盘披萨是这个菜谱的"实例" 你根据同一个菜谱做了第二盘披萨 → 第二盘披萨也是这个菜谱的"实例"
就我的理解,就是其实类的对象和类的实例是同一概念的不同表述,当我们说“某个类的对象”时,99% 的情况就是指“该类的实例”。
#===============================我是分割线================================#
二、开头三行代码中涉及到的知识以及扩展
扯远了,回到开头的三行代码中
第一行
class Module(nn.Module):
- 这是定义一个新类,名字叫Module
- (nn.Module)表示这个类是"继承"自PyTorch的nn.Module类,详情看3
- 想象一下,nn.Module是"武林高手",而Module是"继承了高手内功的徒弟"
第二行
def __init__(self, input_dim=256, dim=64, n_layers=15, n_classes=10):
- 这是类的"初始化方法",相当于给新类"打地基"
__init__是Python的特殊方法(就像类的"出生证明")详情看2self是这个类的"自我指代",相当于"我"这个类的实例- 你看到的那些参数(
input_dim=256等)是这个类的"默认配置",就像手机出厂设置
第三行
super().__init__()
- 这是"关键动作"!调用父类的初始化方法
super()是Python的"神奇指针",指向父类.__init__()就是调用父类的"初始化函数",详情看3、4和6- 没有这行,你的类就像没装引擎的跑车,虽然能跑但会出问题
这其中就涉及到class具体的使用方法了,还是先从最简单的用法说起:
1.类对象支持两种操作:属性引用和实例化。
""""定义一个未继承任何父类的类"""
class my:
i = 123
def f(self):
return 'hello world'
#实例化类
x = my()
#访问类的属性和方法
print("my类的属性i为:",x.i) # my类的属性i为:123
print("my类的方法f为:",x.f()) # my类的方法f为:hello world
2.方法(构造函数):
这是定义在类中的函数,是类的"行为",在类内部用def定义,值得注意的是,这种 def 定义的“方法”必须通过对象或类来调用,第一个参数通常是self
这里涉及到一个点,类有一个名为 __init__() 的特殊方法(构造方法),该方法在类实例化时会自动调用,而其它方法,则需要手动调用,像下面这样:
第一种情况
class Person():
def __init__(self, name, year, weight):
self.name = name
self.year = year
self.weight = weight
print("这个人重量为 %d kg。" % self.weight)
def speak(self):
print("%s 说:我 %d 岁。"%(self.name, self.year))
#创建一个类的实例
p = Person("Bob", 10, 30) #到这里,只会输出:这个人重量为 30 kg。
#调用实例中的方法
p.speak() #输出:Bob 说:我 10 岁。
#当然,__init__也可以不定义任何参数
第二种情况
class Person():
def __init__(self):
print("请你输入个人信息")
def speak(self, name, year, weight):
self.name = name
self.year = year
self.weight = weight
print("%s 说:我 %d 岁。"%(self.name, self.year))
#创建一个类的实例
#这时,类实例化时就不用输入任何参数
p = Person() #到这里,只会输出:请你输入个人信息
#调用实例中的方法
p.speak("Bob", 10, 30) #输出:Bob 说:我 10 岁。
self 代表当前对象实例,同时也要区分实例属性 vs 类属性
class Student:
school = "Python University" # 类属性(所有实例共享)
def __init__(self, name, grade):
self.name = name # 实例属性(每个实例独立)
self.grade = grade # 实例属性
# 使用
s1 = Student("Alice", "A")
s2 = Student("Bob", "B")
print(s1.school) # Python University
print(s2.school) # Python University
Student.school = "AI University" # 修改类属性
print(s1.school) # AI University(所有实例都受影响)
3.继承
子类(派生类 DerivedClassName)会继承父类(基类 BaseClassName)的属性和方法。
class Animal:
def __init__(self, name):
self.name = name
def speak(self):
pass
class Dog(Animal): # Dog 继承 Animal
def speak(self):
return f"{self.name} 喊 汪汪!"
class Cat(Animal): # Cat 继承 Animal
def speak(self):
return f"{self.name} 喊 喵喵!"
dog = Dog("旺财")
cat = Cat("咪咪")
print(dog.speak()) # 旺财 喊 汪汪!
print(cat.speak()) # 咪咪 喊 喵喵!
继承后,你也可以选择直接使用父类中的方法,也可以覆写父类中的方法
@这是单继承
方法一:不覆写,直接引用
class Person():
#定义构造方法
def __init__(self, name, year, weight):
self.name = name
self.year = year
self.weight = weight
def speak(self):
print("%s 说:我 %d 岁。"%(self.name, self.year))
class Student(Person):
def __init__(self, name, year, weight, grade):
#调用父类的构函
Person.__init__(self, name, year, weight)
self.grade = grade
print("数据已录入")
#创建一个类的实例
p = Student("Bob", 12, 30,"六年级") #到这里,输出:数据已录入
#调用实例中的方法
p.speak() #Bob 说:我 12 岁。
方法二:覆写父类的方法
class Person():
#定义构造方法
def __init__(self, name, year, weight):
self.name = name
self.year = year
self.weight = weight
def speak(self):
print("%s 说:我 %d 岁。"%(self.name, self.year))
class Student(Person):
def __init__(self, name, year, weight, grade):
#调用父类的构函
Person.__init__(self, name, year, weight)
self.grade = grade
print("数据已录入")
#覆写父类的方法
def speak(self):
print("%s说:我%d岁,我在读%s "%(self.name, self.year, self.grade))
#创建一个类的实例
p = Student("Bob", 12, 30,"六年级") #到这里,输出:数据已录入
#调用实例中的方法
p.speak() #Bob说:我12岁,我在读六年级
当然,你也可以继承多个父类
@多继承示例
class Person():
#定义构造方法
def __init__(self, name, year, weight):
self.name = name
self.year = year
self.weight = weight
def speak(self):
print("大家好,我叫%s,今年%d岁。"%(self.name, self.year))
class speaker():
def __init__(self, topic):
self.topic = topic
def sample(self):
print("我是一名演说家,我演讲的主题是%s"%self.topic)
class Student(Person, speaker):
def __init__(self, name, year, weight, topic):
#调用父类的构函
Person.__init__(self, name, year, weight)
speaker.__init__(self, topic)
print("数据已录入")
#创建一个类的实例
p = Student("Bob", 12, 30,"python") #到这里,输出:数据已录入
#调用父类实例中的方法
p.speak() #输出:大家好,我叫Bob,今年12岁。
p.sample() #输出:我是一名演说家,我演讲的主题是python
4.super().__init__()用法
super().__init__() 是 Python 中用于调用父类初始化方法的关键代码
以3中的代码为例,他也要区分单继承和多继承的用法。值得注意的是:在4中,我只演示了父类需要参数(比如你自定义的类),而对于开头第三行代码中的那种情况,我放在了6
首先,是单继承的情况
class Person:
def __init__(self, name, year, weight):
self.name = name
self.year = year
self.weight = weight
def speak(self):
print("%s 说:我 %d 岁。" % (self.name, self.year))
class Student(Person):
def __init__(self, name, year, weight, grade):
# 使用 super() 调用父类的 __init__
super().__init__(name, year, weight)
self.grade = grade
print("数据已录入")
# 创建实例
p = Student("Bob", 12, 30, "六年级") # 输出: 数据已录入
p.speak() # 输出: Bob 说:我 12 岁。
其次,是多继承的情况,super().__init__() 在多继承场景下和单继承的用法不完全一样。也就是说,不能简单地把两行
Person.__init__(self, name, year, weight)
speaker.__init__(self, topic)
替换成
super().__init__(...)
原因:super() 遵循 MRO(Method Resolution Order)
- Python 的
super()不是“调用所有父类”,而是按照 MRO 顺序调用下一个类的__init__
在原本的例子中,Student(Person, speaker) 的 MRO 是:Student → Person → speaker → object;而如果使用super().__init__()来直接替代原本的代码Student中就只会调用Person.__init__(),不会自动调用 speaker.__init__()。因此,需要这样写:
class Person():
def __init__(self, name, year, weight, **kwargs):
super().__init__(**kwargs) # 继续传递给下一个父类
self.name = name
self.year = year
self.weight = weight
def speak(self):
print("大家好,我叫%s,今年%d岁。" % (self.name, self.year))
class Speaker(): # 类名建议大写(PEP8)
def __init__(self, topic, **kwargs):
super().__init__(**kwargs) # 继续传递(最终到 object.__init__)
self.topic = topic
def sample(self):
print("我是一名演说家,我演讲的主题是%s" % self.topic)
class Student(Person, Speaker):
def __init__(self, name, year, weight, topic):
# 只需调用一次 super()
super().__init__(name=name, year=year, weight=weight, topic=topic)
print("数据已录入")
# 测试
p = Student("Bob", 12, 30, "python")
p.speak() # 大家好,我叫Bob,今年12岁。
p.sample() # 我是一名演说家,我演讲的主题是python
这样就可以了,同时,这也引出了一个新的东西,**kwargs
5.强大的**kwargs
kwargs = keyword arguments,前面加两个**表示“打包”或“解包”字典
ps:kwargs 只是约定俗成的名字,也可以叫 **options、**config,但大家习惯用 kwargs
核心作用:接收任意数量的关键字参数。当用 **kwargs 接收参数时,Python 会自动把所有关键字参数打包成一个字典。
示例如下:
class Person:
def __init__(self, name, age, **kwargs):
super().__init__(**kwargs) # 把剩下的参数传给下一个父类
self.name = name
self.age = age
class Worker:
def __init__(self, company, salary, **kwargs):
super().__init__(**kwargs) # 继续传递
self.company = company
self.salary = salary
class Employee(Person, Worker):
def __init__(self, name, age, company, salary):
# 一次性传入所有参数
super().__init__(
name=name,
age=age,
company=company,
salary=salary
)
emp = Employee("张三", 30, "腾讯", 20000)
print(emp.name) # 张三
print(emp.company) # 腾讯
额外.开头代码super().__int__()的作用
代码中,super().__int__()为什么不需要参数呢,这点相信很快就能想到,因为他继承的nn.Module.__int__()设计的就不需要参数,所以继承的代码自然就不需要参数,那么问题就来了,那他究竟继承了什么?
通义千问的回答是:继承了nn.Module的内部机制(参数管理、模块注册等),用来将其注册为真正的Pytorch模型
进一步询问注册时如何发生的呢?
首先,当我们写下
self.linear = nn.Linear(10, 5)
正确情况(调用了 super().__init__()):
- nn.Module.__init__() 初始化了两个重要字典:
(1)self._modules = {} # 存子模块
(2)self._parameters = {} # 存参数
- 当你赋值 self.linear = ... 时,
- nn.Module 的 __setattr__ 魔法方法被触发
- 它检测到右边是一个 nn.Module 实例(Linear 继承自 Module)
- 自动把它注册到 self._modules 字典中!
详细可以看这个表

并且,为此,通义千问专门写了一个简化版的逻辑来辅助理解,特殊的__setattr__方法
# 简化版逻辑
def __setattr__(self, name, value):
if isinstance(value, nn.Module):
self._modules[name] = value # ← 自动注册!
elif isinstance(value, nn.Parameter):
self._parameters[name] = value
else:
object.__setattr__(self, name, value) # 普通属性
简单地讲,什么是注册:

也就是说,super().__init__()调用了 nn.Module.__init__(),这个方法在 PyTorch 源码中完成了:
- 初始化 _parameters、_modules、_buffers 等内部字典
- 设置设备(device)、数据类型(dtype)的默认状态
- 建立模块命名空间和层级关系的基础
正是因为父类初始化了特殊的 __setattr__ 机制,当写:
self.linear = nn.Linear(10, 5)
self.weight = nn.Parameter(torch.randn(5))
PyTorch 才能自动识别:
- nn.Linear → 是子模块 → 注册到 _modules
- nn.Parameter → 是可训练参数 → 注册到 _parameters
如果没有 super().__init__(),这些赋值就只是普通的 Python 属性,PyTorch 对它们一无所知。而具体这一注册的这一行为,就涉及到注册方面的原理了。AI说其本质上是一种元数据管理,那究竟是不是,就不在这里探究了,留给下一波了。
最后,感谢通义千问老师的大力支持!!!
1565

被折叠的 条评论
为什么被折叠?



