14岁鲜衣怒马,心比天高,满眼皆是来日可期的万丈风光 24岁历经起落,方知人各有命,世事自有无常法。后来才懂,那股一往无前的少年心气,是此生仅有、不可再生的滚烫宝藏,一旦褪去,便再也寻不回当初一腔孤勇的模样。
上一章我们已经学会了怎么定义一个类,也知道了什么叫对象。
比如你已经能写出这样的代码:
classCat:defmeow(self): print('喵喵喵')
也知道可以这样创建对象:
cat1 = Cat()cat1.meow()
但学到这里,很多人很快就会冒出一个新问题:
类我会写了。对象我也会创建了。 可为什么现在创建出来的对象,看起来都差不多?cat1 和 cat2 有什么区别? 它们怎么才能一个叫“咪咪”,一个叫“雪球”,一个 2 岁,一个 3 岁?
这就要进入这一章的核心了:
实例属性 实例方法
这一章非常关键。因为从这里开始,对象才真正“活起来”。不再只是一个空壳子,而是开始拥有各自不同的数据。
一、先看问题出在哪
先看下面这段代码:
classDog:defbark(self): print('汪汪汪')dog1 = Dog()dog2 = Dog()dog1.bark()dog2.bark()
这段代码当然没问题。 但你会发现,dog1 和 dog2 现在几乎没区别。它们都能叫,但你并不知道谁是谁。
现实里,一只狗通常不会只有“会叫”这一件事。它还会有很多自己的信息,比如:
名字 年龄 颜色 品种
而且这些信息,不同对象之间通常是不一样的。
比如:
dog1 叫大黄,2 岁,黄色dog2 叫豆豆,1 岁,白色
那这些“对象自己的数据”,该放在哪里?
答案就是:实例属性。
二、什么叫实例属性
你可以把实例属性理解成:
某个具体对象自己拥有的数据。
注意,是“某个具体对象自己拥有”。
比如学生对象的姓名、年龄、成绩。 比如汽车对象的品牌、颜色、速度。 比如手机对象的型号、电量、价格。
这些东西都不是整个类统一只有一份,而是每个对象可以各有各的值。
所以它叫实例属性。 因为它属于实例,也就是属于某个具体对象。
你可以先记住一句最朴素的话:
实例属性,就是对象身上的信息。
三、最直接的写法:先创建对象,再给它加属性
在 Python 里,最直观的入门写法是这样的:
classStudent:passstu1 = Student()stu1.name = '张三'stu1.age = 18stu1.score = 95print(stu1.name)print(stu1.age)print(stu1.score)
输出结果:
张三1895
这里发生了什么?
我们先创建了一个 Student 对象。 然后给这个对象动态加上了三个属性:
nameagescore
而这些属性的值,都是属于 stu1 这个对象自己的。
这就已经是实例属性了。
四、不同对象,属性值可以不同
再看一个更完整的例子:
classStudent:passstu1 = Student()stu1.name = '张三'stu1.age = 18stu1.score = 95stu2 = Student()stu2.name = '李四'stu2.age = 19stu2.score = 88print(stu1.name, stu1.age, stu1.score)print(stu2.name, stu2.age, stu2.score)
输出结果:
张三 1895李四 1988
注意这里最重要的一点:
虽然 stu1 和 stu2 都来自同一个 Student 类, 但它们各自保存的数据是不同的。
这就是对象为什么“各有各的数据”。
也就是说:
类是同一个模板 对象是不同个体 实例属性让每个个体都能带着自己不同的信息
这一步想通以后,面向对象的感觉就会一下清晰很多。
五、属性访问为什么要写成 对象.属性
你应该已经发现了,实例属性最常见的访问方式就是:
对象.属性
例如:
stu1.namestu1.agestu1.score
这种写法非常符合现实表达。
不是“有一个孤零零的名字变量”。 而是“张三这个学生对象的名字”。
不是“有一个孤零零的成绩变量”。 而是“这个对象自己的成绩”。
所以你会发现,类和对象让数据的归属关系变得特别清楚。
前面如果你写:
name = '张三'age = 18score = 95
这些变量是散的。 可一旦写成:
stu1.name = '张三'stu1.age = 18stu1.score = 95
信息立刻就被组织起来了。
六、实例方法又是什么
如果说实例属性是对象“有什么”,那实例方法就是对象“能做什么”。
其实上一章你已经见过实例方法了。
比如:
classDog:defbark(self): print('汪汪汪')
这里的 bark(),本质上就是一个实例方法。
为什么叫实例方法?
因为它通常是由某个具体对象来调用的,比如:
dog1.bark()
这不是一个悬空动作,而是“dog1 这个对象执行了 bark 这个行为”。
所以你可以这样记:
实例属性,是对象的数据。 实例方法,是对象的行为。
这两个东西放在一起,才组成了一个完整的对象。
七、实例方法和普通函数,到底差在哪
先看普通函数:
defsay_hello(): print('你好')
调用时这样写:
say_hello()
再看类里的方法:
classStudent:defsay_hello(self): print('你好,我是学生')
调用时这样写:
stu1 = Student()stu1.say_hello()
表面上看,好像都只是打印一句话。 但本质区别在于:
普通函数没有明显归属。 实例方法属于某个对象。
这就意味着,实例方法通常会和这个对象自己的数据配合使用。
比如你很快就会看到,方法里不只是打印固定文字,而是能用到对象自己的姓名、年龄这些属性。
这才是它真正厉害的地方。
八、为什么方法里会有 self
这一章先不把 self 讲得太深,第75章会专门彻底讲透。 但现在你一定要先建立一个非常实用的直觉:
self 通常可以先理解成“当前这个对象自己”。
看代码:
classStudent:defintroduce(self): print('大家好')
这里的 introduce(self) 不是随便多写了一个参数,而是在为“对象自己”预留位置。
因为以后方法很可能要访问这个对象自己的属性,比如姓名、年龄。 没有 self,方法就不知道它现在到底是在处理哪个对象。
你现在先别死抠底层。 先把它粗略理解成:
谁来调用这个方法,self 就代表谁。
这个感觉先有了,后面讲细节时就不会慌。
九、让方法真正用上对象自己的属性
下面这个例子,是这一章最关键的转折点。
classStudent:defintroduce(self): print(f'大家好,我叫{self.name},今年{self.age}岁,成绩是{self.score}分')stu1 = Student()stu1.name = '张三'stu1.age = 18stu1.score = 95stu2 = Student()stu2.name = '李四'stu2.age = 19stu2.score = 88stu1.introduce()stu2.introduce()
输出结果:
大家好,我叫张三,今年18岁,成绩是95分大家好,我叫李四,今年19岁,成绩是88分
这一段非常重要,你一定要看懂。
同一个 introduce() 方法,为什么对 stu1 和 stu2 输出不同?
因为方法里用到的是:
self.nameself.ageself.score
而 self 会指向当前调用这个方法的对象。
也就是说:
stu1.introduce() 里,self 指的是 stu1stu2.introduce() 里,self 指的是 stu2
所以同一个方法,能根据不同对象身上的属性,表现出不同结果。
这就是实例方法真正有意思的地方。
十、现在你要真正理解“对象各有各的数据”了
很多新手学到这里,才会第一次真正感觉到类的价值。
类里方法是同一份。 但对象里的属性值可以不同。 所以同样的方法,落在不同对象上,就会产生不同结果。
这特别像现实世界。
“自我介绍”这个行为,所有学生都会。 但每个学生介绍出来的内容不一样。 因为每个人自己的姓名、年龄、成绩不同。
这就是面向对象最自然的表达方式。
不是重复写很多相似代码, 而是写一个通用的方法,让不同对象带着自己的数据去执行。
十一、实例属性本质上是属于对象,不属于类
这一点特别重要。
比如:
classStudent:passstu1 = Student()stu1.name = '张三'stu2 = Student()stu2.name = '李四'
这里的 name 不是整个 Student 类统一只有一个值。 而是 stu1 有自己的 name,stu2 也有自己的 name。
所以你一定不要把实例属性想成“类里写了一个全局变量”。
更准确的理解是:
同一个类创建出来的每个对象,都可以拥有自己独立的一份属性数据。
这也是为什么对象彼此之间不会互相覆盖。
你改 stu1.name,不会自动改掉 stu2.name。 因为它们本来就不是同一份数据。
十二、用一个更贴近现实的例子再理解一次
比如定义一个 Car 类:
classCar:defshow_info(self): print(f'这辆车的品牌是{self.brand},颜色是{self.color},价格是{self.price}元')
创建两个对象:
car1 = Car()car1.brand = '比亚迪'car1.color = '白色'car1.price = 120000car2 = Car()car2.brand = '特斯拉'car2.color = '黑色'car2.price = 260000car1.show_info()car2.show_info()
输出结果:
这辆车的品牌是比亚迪,颜色是白色,价格是120000元这辆车的品牌是特斯拉,颜色是黑色,价格是260000元
你看,同一个 Car 类,同一个 show_info() 方法, 因为两个对象的属性不同,所以展示出来的信息也不同。
这已经非常接近真实开发里的写法了。
十三、实例方法里不只是读取属性,也可以修改属性
对象的方法不一定只是“展示信息”,也可以修改对象自己的状态。
例如:
classDog:defgrow_up(self): self.age = self.age + 1 print(f'{self.name} 长大了一岁,现在 {self.age} 岁了')dog1 = Dog()dog1.name = '大黄'dog1.age = 2dog1.grow_up()dog1.grow_up()
输出结果:
大黄 长大了一岁,现在 3 岁了大黄 长大了一岁,现在 4 岁了
这说明什么?
说明实例方法不仅能读取 self.age,还能改写 self.age。
也就是说,方法和属性不是分开的两套东西。 方法往往就是围绕对象自己的属性来工作的。
这才是真正完整的对象:
有自己的数据 也有处理自己数据的能力
十四、实例属性不提前赋值会怎样
这里要提前提醒你一个很容易踩的坑。
看下面这段代码:
classStudent:defintroduce(self): print(self.name)stu1 = Student()stu1.introduce()
这段代码会报错。 因为你根本还没有给 stu1 设置 name 属性。
也就是说,虽然 Python 允许你后面动态加属性,但如果你方法里要用某个属性,就得保证它已经存在。
这也是为什么下一章会讲 __init__。 因为用现在这种“先创建对象,再手动一个个补属性”的方式,虽然能帮助你理解实例属性,但写起来并不够稳,也不够规范。
所以这一章的重点,是先让你理解原理。 下一章我们就会把这种写法升级成更标准的方式。
十五、先别嫌这种写法麻烦,它正适合入门
有些人看到这里会觉得:
每次都这样写:
stu1 = Student()stu1.name = '张三'stu1.age = 18stu1.score = 95
是不是有点麻烦。
是的,确实麻烦。 但它有一个非常大的优点:直观。
因为它把“对象”和“属性”之间的关系,几乎是掰开给你看了。
你能清楚看到:
先有对象 再给对象加数据 方法再去使用这些数据
这个理解过程非常值钱。 因为如果一开始就把 __init__、参数传递全塞进来,很多新手会直接把类学成死记语法。
而现在这种写法,虽然笨一点,却特别适合理解本质。
十六、再看一个学生对象的完整例子
下面给你一个适合反复敲一遍的小案例:
classStudent:defintroduce(self): print(f'我叫{self.name},今年{self.age}岁,考试得了{self.score}分')defis_passed(self):if self.score >= 60: print(f'{self.name} 已经及格了')else: print(f'{self.name} 没有及格')stu1 = Student()stu1.name = '张三'stu1.age = 18stu1.score = 95stu2 = Student()stu2.name = '李四'stu2.age = 19stu2.score = 58stu1.introduce()stu1.is_passed()stu2.introduce()stu2.is_passed()
输出结果:
我叫张三,今年18岁,考试得了95分张三 已经及格了我叫李四,今年19岁,考试得了58分李四 没有及格
这个例子很有代表性。
同一个类 两个不同对象 相同的方法 不同的属性值 不同的输出结果
只要你把这一段真正看懂,实例属性和实例方法的关系基本就通了。
十七、实例属性和实例方法,最好成对理解
你不要把它们拆开记。
因为单独记属性,很容易变成“哦,对象里存点数据”。 单独记方法,又容易变成“哦,类里放几个函数”。
真正应该建立起来的感觉是:
实例属性负责保存状态,实例方法负责操作状态。
比如一个学生对象:
属性保存姓名、年龄、成绩 方法负责介绍自己、判断是否及格
比如一个银行账户对象:
属性保存账号、余额 方法负责存钱、取钱、查看余额
只要这个感觉起来了,面向对象就不再是语法堆砌,而是在组织现实角色。
十八、这一章最常见的几个误区
先帮你提前避开几个坑。
第一个误区,把实例属性当成类统一的数据。 不是的,实例属性属于每个对象自己。
第二个误区,觉得方法只是普通函数换个位置。 不完全是。实例方法通常会和对象自己的属性发生关系。
第三个误区,看见 self 就慌。 你现在先把它理解成“当前对象自己”就够用了。
第四个误区,以为对象创建出来就自动有所有属性。 不是。你如果没有赋值,属性就可能还不存在。
这些坑现在先绕开,后面会轻松很多。
十九、本章小练习
你可以自己做两个很适合巩固的小练习。
第一个练习,定义一个 Book 类。
要求:
有 title、author、price 三个实例属性 有一个 show_info() 方法,输出这本书的信息
参考写法:
classBook:defshow_info(self): print(f'书名:{self.title},作者:{self.author},价格:{self.price}元')book1 = Book()book1.title = 'Python 入门'book1.author = '小王'book1.price = 59book2 = Book()book2.title = '数据分析实战'book2.author = '老李'book2.price = 88book1.show_info()book2.show_info()
第二个练习,定义一个 Phone 类。
要求:
有 brand、battery 两个实例属性 有一个 charge() 方法,每次调用让电量加 10,并输出当前电量
参考写法:
classPhone:defcharge(self): self.battery = self.battery + 10 print(f'{self.brand} 当前电量是 {self.battery}%')phone1 = Phone()phone1.brand = '华为'phone1.battery = 50phone1.charge()phone1.charge()
这两个练习都非常典型。 一个练“属性展示”,一个练“方法修改属性”。 你把它们跑通,感觉会非常扎实。
二十、本章总结
这一章是面向对象阶段里非常关键的一章。
实例属性,是某个具体对象自己拥有的数据。 实例方法,是某个具体对象可以执行的行为。 不同对象虽然来自同一个类,但可以拥有不同的属性值。 同一个实例方法作用在不同对象上时,也会因为属性不同而产生不同结果。 方法里的 self,你现在可以先理解成“当前对象自己”。 实例方法不仅可以读取对象属性,也可以修改对象属性。 属性和方法最好放在一起理解:属性负责保存状态,方法负责操作状态。
到这里,你已经真正摸到面向对象的门把手了。 下一章我们继续讲最重要的一个方法:074|init 方法详解:对象创建时到底发生了什么。