Python学习笔记1

部署运行你感兴趣的模型镜像

Class def super三者用法反思1

前情提要:这是我在学过程中做的笔记,思维可能会有一些跳跃和发散,原本我的笔记往往是记在onenote里的,但是我渐渐发现,记在它里面,如果我想在别的设备上查阅,我就还得想共享和同步,所以还是记在csdn上吧,也算是赛博备忘录吧。

本来,因为怕里面有错误,再加上我写的时候也是想到哪里就写到哪里,逻辑可能有一些混乱怕影响到大佬们,所以小弟是想将其设置仅自己可见。但我发现我不会,而且想了想。反正是小白,无所谓了。

如果有不足,还望各位大佬可以不吝赐教,小弟在此感激不敬!!!

       这段代码在神经网络的模型的网络设计中十分常见,其实对于class,我只知道在这段代码中,是定义一个新类,名叫Module,但是我不太明白(nn.Module)这是什么意思,我只知道这是要这个类继承来自Pytorchnn.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的特殊方法(就像类的"出生证明")详情看2
  • self是这个类的"自我指代",相当于"我"这个类的实例
  • 你看到的那些参数(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说其本质上是一种元数据管理,那究竟是不是,就不在这里探究了,留给下一波了。

    最后,感谢通义千问老师的大力支持!!!

    您可能感兴趣的与本文相关的镜像

    Python3.8

    Python3.8

    Conda
    Python

    Python 是一种高级、解释型、通用的编程语言,以其简洁易读的语法而闻名,适用于广泛的应用,包括Web开发、数据分析、人工智能和自动化脚本

    评论
    成就一亿技术人!
    拼手气红包6.0元
    还能输入1000个字符
     
    红包 添加红包
    表情包 插入表情
     条评论被折叠 查看
    添加红包

    请填写红包祝福语或标题

    红包个数最小为10个

    红包金额最低5元

    当前余额3.43前往充值 >
    需支付:10.00
    成就一亿技术人!
    领取后你会自动成为博主和红包主的粉丝 规则
    hope_wisdom
    发出的红包
    实付
    使用余额支付
    点击重新获取
    扫码支付
    钱包余额 0

    抵扣说明:

    1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
    2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

    余额充值