本文为 Python 60 Days Challenge 课程学习整理,原作者与原 Notebook 链接见「课程资料」。代码按章节顺序运行;语法示意和故意报错的反例已单独标注。
文中保留的截图、性能表和数值属于原讲义的历史示例,不是本次修订后的重新训练结果。实际输出受数据、版本、随机种子和训练预算影响。
打卡任务
- 阅读本节讲义或 Notebook
- 跑通示例代码
- 整理 3 个关键点
- 完成当日打卡输出
课程资料
- 训练营讲义:https://docs.qq.com/doc/DTFhndExSRGNHS3B6?nlc=1
- GitHub 仓库:https://github.com/cheer932041235/Python60DaysChallenge
- 本日 Notebook:day28 类的定义和方法.ipynb
学习内容
之前大家已经接触过类的概念,我们也反复强调了类的实例化。
为了避免有的同学之前没看过复试班的内容或者已经遗忘,我们来简单的复习下
类是对属性和方法的封装,可以理解为模板,通过对模板实例化可以实现调用这个类的属性和方法。比如创建一个随机森林类,然后就可以调用他的训练和预测方法。
现在我们来学一下自己定义一个类,这会让我们对于类这个对象理解的更加深刻
ps:类的操作很多,我们这里只说一些在深度学习领域最常见和通用的
一个常见的类的定义包括了:
- 关键字class
- 类名
- 语法固定符号冒号(:)
- 一个初始化函数__init__(self)
注意:注意:init左右各有两个下划线__,需要传入self这个特殊的参数。
Pass占位符和缩进
class ClassName: # 类名通常遵循大驼峰命名法 (UpperCamelCase),即每个单词的首字母都大写,class是定义类的关键词
# 类的代码块 (可以包含属性定义、方法定义等)
pass # pass 是一个占位符,表示这里暂时没有任何内容
以下是语法示意或故意报错的反例,不是需要整段运行的正常程序。
class ClassName: # 类名通常遵循大驼峰命名法 (UpperCamelCase),即每个单词的首字母都大写,
# # 类的代码块 (可以包含属性定义、方法定义等)
# pass # pass 是一个占位符,表示这里暂时没有任何内容
许多时候,当规划好准备写一个函数、或者一个类,关键词定义后,会先用pass占位,避免运行错误,等到想好写什么再去补上
比如def、class这些定义的关键词后,必须有一个有占据缩进位置的代码块。
还有下面这些依赖缩进的语句,都可以用pass语句来占位
# 条件语句
x = 10
if x > 5:
pass# 如果这里是空的,就会报错,可以注释试一下,即使x=1也会报错
else:
print("x is not greater than 5")
# 循环语句
for i in range(3):
pass # 循环体是空的
try:
# 尝试执行的代码
print("hh")
except SomeError:
pass# 捕获到异常后,这里需要代码
finally:
pass# 无论如何都会执行的代码块,也需要内容
总结:
Python 通过缩进来定义代码块的结构。当解释器遇到像 def, class, if, for 这样的语句,并且后面跟着冒号 : 时,它就期望接下来会有一个或多个缩进的语句来构成这个代码块。如果它没有找到任何缩进的语句(即代码块是空的),它就无法确定这个结构的范围,因此会抛出 IndentationError。
pass 语句的存在就是为了解决这个问题:它本身不执行任何操作,但它是一个有效的 Python 语句。所以,当你需要一个语法上存在的代码块,但又暂时不想在其中放入任何实际的逻辑时,pass 就是一个完美的占位符,它告诉解释器:“这里有一个代码块,但它什么也不做。”
类的初始化方法
初始化方法又叫构造方法、特殊方法
类有2种方法
- 初始化方法,
- 普通放大
class Teacher: # 这里不需要括号
def __init__(self): #初始化方法,这里没有传入参数
self.name = "Susan" # 给类定义一些属性
self.subject = "English"
self.age = 33
Teacher = Teacher() # 创建一个Teacher类的实例
print(Teacher.name) # 输出: Susan
class Teacher:
def __init__(self, name, age):# 初始化方法,传入了参数
self.name = name # 外界的参数,需要通过self.xxx来复制给类自己的属性
self.age = age
self.subject = "English" # 这个属性仍然是在创建时就设定好的
# 创建一个Teacher对象的例子,构造方法的参数必须
teacher = Teacher("Susan", 33) # 如果在初始化方法中设置了非默认的参数,那么外界就必须要传入才行
print(teacher.name) # 输出: Susan
print(teacher.age) # 输出: 33
print(teacher.subject) # 输出: English
其中,self.xx是用来表明这个属性“归属于”这个类自己的(self)。比如self.name,就代表着:“自己的名字”,self等于“自己”,这个self指向类的实例化地址,传入的self.xx是它的属性。
以后要是用它的属性值,即使是从外界参数传入的,前面也必须加上self.xx,否则传入的参数没价值(例外的情况我们不提)
类的普通方法
除了init方法(初始化方法,又名构造方法),还包含一些普通方法(自己定义)
普通方法和init方法的差别在于,init方法是类的构造方法,当创建对象时,会自动调用init方法----只要你创建这个类对象了,这个init函数就会执行
普通方法是只有你调用类的这个方法的时候,函数才会执行
| 特性 | init 方法 | 普通方法 |
|---|---|---|
| 调用时机 | 创建实例时自动调用 | 手动通过实例调用 |
| 是否需要显式调用 | 否 | 是 |
| 默认名称 | 必须是 init | 自定义 |
| 主要用途 | 初始化实例属性 | 实现类的行为逻辑 |
| 参数要求 | 第一个参数必须是 self | 第一个参数必须是 self |
| 返回值 | 必须返回 None(隐式) | 可以返回任意类型的值 |
class Teacher:
def __init__(self):
self.name = "Susan"
self.subject = "English"
self.age = 33
def teach_lesson(self):
print("上课中")
def criticize(self):
print("批评人")
t = Teacher()
t.teach_lesson() # 调用类的方法
t.criticize()
print(t.name)
class Teacher:
# 初始化方法接受参数以动态设置教师的属性
def __init__(self, name, subject, age):
self.name = name
self.subject = subject
self.age = age
# 不是init的都叫做普通方法
# 普通方法,模拟教师上课的行为
def teach_lesson(self):
print(f"{self.name}正在教{self.subject}。")
# 另一个普通方法,模拟教师批评学生的行为
def criticize(self, student_name):
print(f"{self.name}正在批评{student_name}。")
# 创建Teacher类的实例
teacher = Teacher("Susan", "English", 33)
# 调用教师的方法
teacher.teach_lesson()
teacher.criticize("John")#普通方法的参可以等到调用该方法的时候再传
类的继承
类已经是比较优秀的封装了,封装了函数、封装了属性
正如装饰器进一步封装了函数的可复用的功能,装饰器函数封装了函数
那么有没有东西可以进一步封装类呢?这就引出了类的继承
在面向对象编程中,继承允许一个类(子类)继承另一个类(父类)的属性和方法,从而实现代码复用和功能扩展。子类可以:
- 复用父类的代码(无需重新实现)。
- 重写父类的方法(修改或增强功能)。
- 添加新的方法和属性(扩展功能)。
仔细观察下方实例代码的注释,写的非常详细
# 先沿用之前定义的teacher类
class Teacher:
def __init__(self, name, subject, age):
self.name = name
self.subject = subject
self.age = age
def teach_lesson(self):
print(f"{self.name}正在教{self.subject}。")
def criticize(self, student_name):
print(f"{self.name}正在批评{student_name}。")
# 继承 Teacher 类,起名特级教师
class MasterTeacher(Teacher): # 1. 继承需要在括号中指定父类
def __init__(self, name, subject, age, experience_years):# 2. 继承的时候需要调用父类的构造方法,所以需要传入父类的参数,同时也可以传入自己的参数
# 调用父类的构造方法初始化基本属性
super().__init__(name, subject, age) # 3. 调用父类的构造方法,这里的super()是一个内置函数,返回父类的实例
# 4. 此时子类自动拥有了父类的属性和方法
# 添加子类特有的属性
self.experience_years = experience_years # 5. 子类特有的属性可以在这里定义
# 重写父类方法,增强功能-----如果子类定义了与父类同名的方法,子类实例会优先调用子类的方法。
def teach_lesson(self): # 6. 重写父类的方法
print(f"{self.name}(特级教师)正在用高级方法教授{self.subject}。")
# 新增子类特有的方法
def give_lecture(self, topic):
print(f"{self.name}正在举办关于{topic}的讲座。")
# 创建子类实例
master = MasterTeacher("王教授", "数学", 45, 20)
# 调用继承的方法
master.teach_lesson() # 调用重写的父类的方法
master.criticize("李同学") # 调用父类的方法,如果不修改方法,则可以直接继承父类的方法
# 调用子类特有的方法
master.give_lecture("微积分") # 调用子类新增的方法
# super()函数 除了在构造方法中使用,还可以在其他方法中使用
# 定义一个父类
class Animal:
def __init__(self, name, age):
self.name = name
self.age = age
def speak(self):
print(f"{self.name} 发出声音")
class Dog(Animal):
def speak(self):
super().speak() # 先调用父类的方法
print("汪汪叫") # 再添加子类的行为
dog = Dog("旺财", 3)
dog.speak()
所以重写父类的方法,也包含2种
- 直接重写:本质是会优先用子类的同名方法,完全替代父类方法,父类逻辑不会执行。
- 使用super()重写,保留父类方法的核心逻辑,并在其基础上扩展。
Day28 类的定义和方法
本文采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处。
评论交流
欢迎留下你的想法