查看全课大纲(第 32 / 51 节)
- 1.Python 是什么?为什么值得学?
- 2.如何为 Python 编程准备一个“全家桶”环境?
- 3.Python 有哪几种执行方法?
- 4.行与缩进到底有什么用?
- 5.注释有什么用,怎么写?
- 6.变量:如何给数据起个好名字?
- 7.运算符的优先级和常见坑点是什么?
- 8.Python 中,数字和布尔值究竟有哪几种?
- 9.输入函数 `input()` 和文件读取 `open()` 怎么用?
- 10.如何将程序的结果展示给用户或保存下来?
- 11.CSV 文件怎么读怎么写才方便?
- 12.列表是什么,如何创建和访问它?
- 13.列表的修改操作,有哪些方法?
- 14.列表切片为什么能取到部分元素?排序和查找又该怎么用?
- 15.判断语句:如何让程序根据条件做不同的事?
- 16.for 循环如何遍历序列中的元素?
- 17.while 循环什么时候才会停?
- 18.while 循环的结束条件,你写对了吗?
- 19.循环中途想提前结束,怎么办?
- 20.`break` 和 `continue` 到底有什么区别?
- 21.列表推导式如何让代码更简洁高效?
- 22.字符串怎么定义、拼接和操作?
- 23.元组:为什么定义单个元素时要加逗号?
- 24.集合(set)怎么用?如何快速去重和做集合运算?
- 25.字典是什么,为什么比列表更方便?
- 26.字典的增删改查与遍历,如何避免 KeyError?
- 27.函数里的变量,为什么有的能用有的不能用?
- 28.在函数内部修改全局变量,为什么有时不生效?
- 29.函数里能修改全局变量吗?返回值怎么用?
- 30.匿名函数:如何用 lambda 快速定义简单函数?
- 31.面向对象编程是什么,为什么需要它?
- 32.类中的方法和属性到底怎么定义和使用?
- 33.如何控制对对象内部属性和方法的访问?
- 34.异常发生时,程序会怎样继续执行?
- 35.如何组织代码:包和模块
- 36.`if __name__ == '__main__'` 到底有什么用?
- 37.如何用 IDE 的 Debug 功能排查程序错误?
- 38.Matplotlib 绘图的第一步该做什么?
- 39.Figure 是什么,如何设置图形尺寸?
- 40.如何自定义 Matplotlib 的坐标轴?
- 41.如何为图表添加图例,让不同曲线一目了然?
- 42.饼图怎么画才能直观展示数据占比?
- 43.如何用 Matplotlib 绘制散点图?
- 44.柱状图怎么画?
- 45.如何使用 Matplotlib 绘制等高线图?
- 46.如何在 Matplotlib 中绘制子图?
- 47.zip 函数如何把多个列表“打包”在一起?
- 48.如何给可迭代对象加上索引?
- 49.random 模块如何生成随机数?
- 50.math 模块怎么用?
- 51.Counter 如何快速统计元素出现次数?
类中的方法和属性到底怎么定义和使用?
约 14 分钟
小象实战讲义 · Python 编程从入门到实践
在上一节我们初步了解了面向对象编程的概念。本节将深入类的内部,学习如何为类定义方法(行为)和属性(状态)。你将学会如何创建具有特定属性和行为的对象,并理解对象初始化、实例属性与类属性的区别。
💡 核心导读
- 类的基本定义:使用
class关键字定义类,类名采用大驼峰命名法。 - 方法的定义与 self 参数:类中的方法第一个参数必须是
self,它指向当前对象实例。 - 构造方法
__init__:在对象创建时自动调用,用于初始化对象的属性。 - 实例属性与类属性:实例属性属于具体对象,类属性属于类本身,被所有实例共享。
- 属性的访问与修改:掌握通过对象和类名访问、修改属性的正确方式。
定义类与基本方法
类是对象的蓝图。在 Python 中,使用 class 关键字定义类,类名通常采用大驼峰命名法(每个单词首字母大写,无下划线)。
关键点:类中定义的方法,第一个参数必须是 self。self 是一个指向当前对象实例的引用。当通过某个对象调用方法时,Python 会自动将该对象作为 self 参数传入。因此,在定义方法时,即使方法不需要其他参数,也必须写上 self。
定义好类之后,就可以创建对象(称为实例化)并调用其方法。一个类可以创建多个对象,每个对象都是独立的。可以用内置函数 id() 观察对象在内存中的地址:两个对象的地址不同(具体数值每次运行可能不同,所以下面只比较它们是否相同)。
class Dog:
def eat(self):
print("小狗正在啃骨头...")
def drink(self):
print("小狗正在喝水")
# 创建一个小狗对象,变量名为 wang_cai
wang_cai = Dog()
# 调用对象的方法
wang_cai.eat()
wang_cai.drink()
# 再创建一个对象:阿福
a_fu = Dog()
wang_cai.eat()
a_fu.drink()
# 用 id() 查看对象的内存地址:两个对象的地址不同
print("两个对象的 id 是否相同:", id(wang_cai) == id(a_fu))小狗正在啃骨头...
小狗正在喝水
小狗正在啃骨头...
小狗正在喝水
两个对象的 id 是否相同: False构造方法与实例属性
我们希望每只小狗创建时就有自己的名字、品种等信息。这需要在对象创建时就进行初始化,使用构造方法 __init__。
__init__ 方法在对象被创建时自动调用。我们可以在其中为对象设置初始状态,即定义实例属性。
class Dog:
# 构造方法,在创建对象时自动调用
def __init__(self, gender, variety, name):
# 使用 self.属性名 = 值 来定义实例属性
self.gender = gender # 性别
self.variety = variety # 品种
self.name = name # 名字
def get_property(self):
# 在类内部的其他方法中,通过 self 访问实例属性
print("性别:{}, 品种:{}, 名字:{}".format(self.gender, self.variety, self.name))
# 创建对象时,必须传入 __init__ 方法中定义的参数(self 除外)
wangcai = Dog("雄性", "金毛", "旺财")
wangcai.get_property()
yuanbao = Dog("雌性", "哈士奇", "来福")
yuanbao.get_property()性别:雄性, 品种:金毛, 名字:旺财
性别:雌性, 品种:哈士奇, 名字:来福对象创建过程理解:
- 执行
Dog(...)时,Python 在内存中为新的Dog对象申请空间。 - 自动调用该对象的
__init__方法,并将我们传入的参数(如"雄性", "金毛", "旺财")传递给它。 - 在
__init__方法内部,通过self.xxx = ...将数据存储为对象的属性。 - 初始化完成后,新对象的引用(内存地址)被返回,并赋值给变量(如
wangcai)。
如果不定义 __init__ 方法,Python 会提供一个默认的、什么都不做的 __init__ 方法。
实例属性与类属性
属性分为两种:实例属性和类属性。
- 实例属性:属于具体的对象。通过
self.属性名 = 值在__init__或其他方法中定义。每个对象的实例属性互不影响。 - 类属性:属于类本身。在类内部、方法外部直接定义。所有该类的对象共享同一个类属性。
class Dog:
# 类属性,在类内部、方法外部定义
weight = 10 # 所有狗的默认体重
def __init__(self, name):
# 实例属性
self.name = name
# 创建两个对象
wang_cai = Dog("旺财")
yuan_bao = Dog("元宝")
# 访问类属性:通过类名或对象名
print("类属性 Dog.weight: {}".format(Dog.weight))
print("旺财访问 weight: {}".format(wang_cai.weight))
print("元宝访问 weight: {}".format(yuan_bao.weight))
# 修改类属性:通过类名修改,会影响到所有对象
Dog.weight = 20
print("修改类属性后:")
print("旺财的 weight: {}".format(wang_cai.weight))
print("元宝的 weight: {}".format(yuan_bao.weight))
print("Dog类的 weight: {}".format(Dog.weight))类属性 Dog.weight: 10
旺财访问 weight: 10
元宝访问 weight: 10
修改类属性后:
旺财的 weight: 20
元宝的 weight: 20
Dog类的 weight: 20修改类属性:应通过类名进行修改,这会影响到所有对象。
一个重要的坑:如果通过对象去给一个与类属性同名的属性赋值,并不会修改类属性,而是会为该对象创建一个新的实例属性,从而“屏蔽”掉类属性。
class Dog:
weight = 20 # 类属性
def __init__(self, name):
self.name = name
wang_cai = Dog("旺财")
yuan_bao = Dog("元宝")
# 通过对象给 weight 赋值
wang_cai.weight = 30 # 这为旺财创建了一个实例属性 weight=30
print("通过旺财对象赋值后:")
print("旺财的 weight: {}".format(wang_cai.weight)) # 访问的是旺财自己的实例属性 30
print("元宝的 weight: {}".format(yuan_bao.weight)) # 访问的仍是类属性 20
print("Dog类的 weight: {}".format(Dog.weight)) # 类属性本身没有变,还是 20通过旺财对象赋值后:
旺财的 weight: 30
元宝的 weight: 20
Dog类的 weight: 20何时使用类属性?当某个属性是整个类所有对象共有的、不常改变的,并且你希望通过修改它来影响所有对象时,才使用类属性。
📝 动手练一练
- 创建一个
Student类,要求:- 构造方法
__init__接收两个参数:name(姓名)和student_id(学号),并将它们保存为实例属性。 - 定义一个类属性
school,初始值为"小象中学"。 - 定义一个方法
introduce,打印"我是{school}的{name},学号是{student_id}。"。
- 构造方法
👉 点击查看参考答案
思路:按照题目要求,在类定义中正确放置类属性和实例属性,在方法中通过 self 访问实例属性,通过 类名.类属性 访问类属性。
class Student:
# 类属性
school = "小象中学"
def __init__(self, name, student_id):
# 实例属性
self.name = name
self.student_id = student_id
def introduce(self):
# 访问类属性推荐使用类名,更清晰
print("我是{}的{},学号是{}。".format(Student.school, self.name, self.student_id))
# 测试
stu1 = Student("张三", "2024001")
stu1.introduce()
# 修改类属性
Student.school = "小象第一中学"
stu2 = Student("李四", "2024002")
stu2.introduce()
stu1.introduce() # stu1的自我介绍也会使用新的学校名我是小象中学的张三,学号是2024001。
我是小象第一中学的李四,学号是2024002。
我是小象第一中学的张三,学号是2024001。常见错误
| 错误写法 | 现象或报错 | 原因 | 正确写法 |
|---|---|---|---|
def eat():(在类中定义方法) | 调用时 TypeError: Dog.eat() takes 0 positional arguments but 1 was given | 类的方法在调用时,Python 会自动传入调用者对象作为第一个参数。定义时缺少 self 参数来接收它。 | def eat(self): |
wangcai = Dog | 调用方法时报错 TypeError: Dog.eat() missing 1 required positional argument: 'self' | Dog 是类本身,Dog() 才是创建对象。直接 Dog 赋值只是将类这个“模板”赋给了变量,没有创建实际对象。 | wangcai = Dog() |
wangcai = Dog("旺财")(但 __init__ 定义为 def __init__(self):) | TypeError: Dog.__init__() takes 1 positional argument but 2 were given | 创建对象时传入的参数个数,必须与 __init__ 方法(除 self 外)定义的参数个数一致。 | 修改 __init__ 定义以接收参数,或创建时不传参。 |
通过对象修改类属性:obj.class_attr = new_value | 其他对象访问该类属性时值未变。 | 这实际上是为 obj 创建了一个同名的实例属性,覆盖了对类属性的访问,并未修改类属性本身。 | 通过类名修改:ClassName.class_attr = new_value |
小结
- 使用
class ClassName:定义类,类名采用大驼峰命名法。 - 类中的方法第一个参数必须是
self,它代表调用该方法的对象实例。 - 构造方法
__init__在对象创建时自动调用,用于初始化对象的实例属性(通过self.attr = value定义)。 - 实例属性属于具体对象,不同对象间独立。类属性在类内部方法外定义,属于类,被所有实例共享。
- 访问或修改类属性应通过类名(
ClassName.attr)。通过对象给类属性同名变量赋值会创建新的实例属性,而非修改类属性。
📋 行动清单
- 将
Dog类的__init__方法多加一个age参数,并修改get_property方法将其打印出来。 - 在
Student类的练习中,尝试通过stu1.school = "某校"赋值,然后分别打印stu1.school,stu2.school,Student.school,观察结果,理解实例属性对类属性的覆盖。 - 思考:如果有一个属性,希望每个
Dog对象都有一份独立的副本,且创建时就有初始值,应该把它定义成实例属性还是类属性?
—— 小象教研组
配套资料 · 直接下载
课件、代码、数据集等配套资料全部免费,点击即可直接下载,无需分享或解锁。
- 下载第4章代码包:面向对象基础(含1 个 Notebook)ZIP · 2KB