Python(三十二) 类的属性、方法和 @property 详解
1. 本篇学习目标
在 Python 面向对象编程中,类不仅可以创建对象,还可以把数据和行为组织在一起。
本篇重点讲清楚这些内容:
- 什么是属性
- 什么是实例属性
- 什么是类属性
- 什么是方法
- 什么是实例方法
- 什么是类方法
- 什么是静态方法
self和cls的区别- 什么是
@property @property的 getter、setter、deleter- 属性和方法的命名建议
- 常见错误和注意事项
本篇适合在学生已经学过“类和对象”的基础上讲解。
2. 先回顾类和对象
类是模板,对象是根据模板创建出来的具体实例。
例如:
class Student:
pass
stu = Student()
这里:
Student 是类。
stu 是对象,也叫 Student 类的实例。
类中通常会包含两类内容:
属性:对象或类保存的数据。
方法:对象或类可以执行的行为。
例如学生:
属性:姓名、年龄、成绩、学校
方法:自我介绍、修改成绩、判断是否及格
3. 什么是属性
属性就是类或对象身上保存的数据。
通俗地说:
属性表示“有什么”。
例如,一个学生对象有什么?
姓名
年龄
成绩
班级
学校
代码示例:
class Student:
pass
stu = Student()
stu.name = "张三"
stu.age = 18
stu.score = 90
print(stu.name)
print(stu.age)
print(stu.score)
输出:
张三
18
90
这里的 name、age、score 都是对象属性。
4. 属性的两大类
Python 类中的属性常见分为两类:
| 类型 | 属于谁 | 是否共享 |
|---|---|---|
| 实例属性 | 属于具体对象 | 每个对象各有一份 |
| 类属性 | 属于类本身 | 所有对象共享 |
简单理解:
每个对象不同的数据,用实例属性。
所有对象相同的数据,用类属性。
例如:
学生姓名:每个学生不同,用实例属性。
学生成绩:每个学生不同,用实例属性。
学校名称:所有学生相同,可以用类属性。
5. 实例属性
实例属性是属于某个具体对象的数据。
对象也叫实例,所以实例属性也叫对象属性。
最常见的写法是在 __init__() 中定义实例属性。
class Student:
def __init__(self, name, age, score):
self.name = name
self.age = age
self.score = score
stu = Student("张三", 18, 90)
print(stu.name)
print(stu.age)
print(stu.score)
输出:
张三
18
90
这里:
self.name 是实例属性。
self.age 是实例属性。
self.score 是实例属性。
6. 为什么实例属性写在 init 中
__init__() 是对象创建后自动执行的初始化方法。
把实例属性写在 __init__() 中有几个好处:
对象创建时就拥有完整数据。
类的结构更清楚。
不容易忘记给对象添加属性。
推荐写法:
class Student:
def __init__(self, name, score):
self.name = name
self.score = score
不太推荐初学阶段大量这样写:
class Student:
pass
stu = Student()
stu.name = "张三"
stu.score = 90
虽然可以运行,但类的结构不清楚。
教学建议:
实例属性优先写在 __init__ 中。
7. 多个对象的实例属性互不影响
同一个类可以创建多个对象。
每个对象都有自己的实例属性。
class Student:
def __init__(self, name, score):
self.name = name
self.score = score
stu1 = Student("张三", 90)
stu2 = Student("李四", 85)
print(stu1.name, stu1.score)
print(stu2.name, stu2.score)
输出:
张三 90
李四 85
修改 stu1 的属性,不会影响 stu2。
stu1.score = 95
print(stu1.score)
print(stu2.score)
输出:
95
85
说明:
stu1 和 stu2 是两个不同对象。
它们的实例属性互不影响。
8. 类属性
类属性是属于类本身的数据。
类属性通常写在类里面、方法外面。
示例:
class Student:
school = "希望中学"
def __init__(self, name):
self.name = name
stu1 = Student("张三")
stu2 = Student("李四")
print(Student.school)
print(stu1.school)
print(stu2.school)
输出:
希望中学
希望中学
希望中学
这里:
school 是类属性。
它属于 Student 类。
所有 Student 对象都可以访问它。
9. 类属性适合保存什么
类属性适合保存所有对象共享的数据。
例如:
class Student:
school = "希望中学"
因为同一批学生可能都属于同一所学校。
再比如:
class Product:
tax_rate = 0.13
如果所有商品使用同一个税率,可以把税率写成类属性。
适合类属性的数据:
所有对象共享的学校名称
统一折扣率
统一税率
对象计数器
配置常量
不适合类属性的数据:
每个学生的姓名
每个学生的成绩
每个商品的库存
每个账户的余额
这些应该使用实例属性。
10. 实例属性和类属性对比
| 对比项 | 实例属性 | 类属性 |
|---|---|---|
| 属于谁 | 具体对象 | 类本身 |
| 定义位置 | 通常在 __init__ 中 |
类中、方法外 |
| 是否共享 | 每个对象各有一份 | 所有对象共享 |
| 访问方式 | 对象.属性 |
类名.属性 |
| 常见用途 | 姓名、成绩、余额 | 学校、税率、计数器 |
示例:
class Student:
school = "希望中学"
def __init__(self, name, score):
self.name = name
self.score = score
stu = Student("张三", 90)
print(stu.name)
print(stu.score)
print(Student.school)
输出:
张三
90
希望中学
11. 修改类属性
修改类属性时,推荐通过类名修改。
class Student:
school = "希望中学"
stu1 = Student()
stu2 = Student()
Student.school = "实验中学"
print(stu1.school)
print(stu2.school)
print(Student.school)
输出:
实验中学
实验中学
实验中学
因为 school 是类属性,通过类名修改后,所有对象访问到的都是新值。
12. 通过对象修改类属性的陷阱
看下面的代码:
class Student:
school = "希望中学"
stu1 = Student()
stu2 = Student()
stu1.school = "实验中学"
print(stu1.school)
print(stu2.school)
print(Student.school)
输出:
实验中学
希望中学
希望中学
为什么?
因为:
stu1.school = "实验中学"
不是修改类属性。
而是给 stu1 新增了一个同名实例属性 school。
此时:
stu1 有自己的 school。
stu2 没有自己的 school,所以去类中找 school。
Student.school 仍然是原来的值。
教学记忆:
类属性用类名改,不要随手用对象改。
13. 属性查找顺序
当我们访问:
对象.属性
Python 通常会按下面的顺序查找:
1. 先找对象自己的实例属性。
2. 如果对象没有,再去类中找类属性。
示例:
class Student:
school = "希望中学"
stu = Student()
print(stu.school)
stu.school = "实验中学"
print(stu.school)
print(Student.school)
输出:
希望中学
实验中学
希望中学
第一次:
stu 没有实例属性 school,所以找到类属性 Student.school。
第二次:
stu 自己有实例属性 school,所以优先使用实例属性。
14. 类属性统计对象数量
类属性常见用途之一是统计创建了多少个对象。
class Student:
count = 0
def __init__(self, name):
self.name = name
Student.count += 1
stu1 = Student("张三")
stu2 = Student("李四")
stu3 = Student("王五")
print(Student.count)
输出:
3
这里:
count 是类属性。
每创建一个 Student 对象,count 就加 1。
注意:
Student.count += 1
这里推荐写类名 Student,让学生清楚这是在修改类属性。
15. 可变类属性的坑
类属性如果是列表、字典、集合这类可变对象,要非常小心。
看例子:
class Student:
hobbies = []
def __init__(self, name):
self.name = name
stu1 = Student("张三")
stu2 = Student("李四")
stu1.hobbies.append("篮球")
print(stu1.hobbies)
print(stu2.hobbies)
输出:
['篮球']
['篮球']
为什么 stu2 也有篮球?
因为:
hobbies 是类属性。
所有对象共享同一个列表。
如果每个学生应该有自己的爱好列表,就应该写成实例属性。
class Student:
def __init__(self, name):
self.name = name
self.hobbies = []
stu1 = Student("张三")
stu2 = Student("李四")
stu1.hobbies.append("篮球")
print(stu1.hobbies)
print(stu2.hobbies)
输出:
['篮球']
[]
教学记忆:
每个对象都应该独立拥有的数据,不要写成可变类属性。
16. 什么是方法
方法就是定义在类里面的函数。
通俗地说:
属性表示对象有什么。
方法表示对象能做什么。
例如学生:
属性:姓名、年龄、成绩
方法:自我介绍、学习、修改成绩
示例:
class Student:
def introduce(self):
print("大家好,我是学生")
stu = Student()
stu.introduce()
输出:
大家好,我是学生
17. 方法的三大类
Python 类中的方法常见分为三类:
| 方法类型 | 装饰器 | 第一个参数 | 主要操作对象 |
|---|---|---|---|
| 实例方法 | 无 | self |
实例对象 |
| 类方法 | @classmethod |
cls |
类本身 |
| 静态方法 | @staticmethod |
无固定参数 | 普通工具逻辑 |
简单理解:
实例方法操作对象自己的数据。
类方法操作类自己的数据。
静态方法只是放在类里的普通函数。
18. 实例方法
实例方法是最常见的方法。
特点:
定义在类中。
第一个参数是 self。
通过对象调用。
可以访问实例属性。
示例:
class Student:
def __init__(self, name, score):
self.name = name
self.score = score
def show_info(self):
print(f"{self.name} 的成绩是 {self.score}")
stu = Student("张三", 90)
stu.show_info()
输出:
张三 的成绩是 90
这里:
show_info() 是实例方法。
self 表示当前调用方法的对象。
19. self 是什么
self 表示当前对象。
通俗记忆:
谁调用方法,self 就是谁。
示例:
class Student:
def __init__(self, name):
self.name = name
def say_name(self):
print(self.name)
stu1 = Student("张三")
stu2 = Student("李四")
stu1.say_name()
stu2.say_name()
输出:
张三
李四
执行:
stu1.say_name()
此时 self 是 stu1。
执行:
stu2.say_name()
此时 self 是 stu2。
20. 实例方法中访问属性
实例方法中访问实例属性,要写:
self.属性名
示例:
class Student:
def __init__(self, name, score):
self.name = name
self.score = score
def is_passed(self):
return self.score >= 60
stu = Student("张三", 90)
print(stu.is_passed())
输出:
True
如果不写 self:
def is_passed(self):
return score >= 60
会报错,因为 Python 不知道 score 是谁。
21. 实例方法可以修改实例属性
示例:修改学生成绩。
class Student:
def __init__(self, name, score):
self.name = name
self.score = score
def update_score(self, new_score):
self.score = new_score
stu = Student("张三", 80)
stu.update_score(95)
print(stu.score)
输出:
95
这里:
self.score = new_score
修改的是当前对象自己的 score 属性。
22. 实例方法可以有参数和返回值
方法和普通函数一样,可以有参数,也可以有返回值。
class Calculator:
def add(self, a, b):
return a + b
calc = Calculator()
result = calc.add(3, 5)
print(result)
输出:
8
注意:
self 是 Python 自动传入的。
调用时只需要传 a 和 b。
调用:
calc.add(3, 5)
不需要写:
calc.add(calc, 3, 5)
23. 类方法
类方法使用 @classmethod 装饰器。
类方法第一个参数通常写成 cls。
cls 表示当前类。
示例:
class Student:
count = 0
def __init__(self, name):
self.name = name
Student.count += 1
@classmethod
def show_count(cls):
print(f"当前学生数量:{cls.count}")
stu1 = Student("张三")
stu2 = Student("李四")
Student.show_count()
输出:
当前学生数量:2
这里:
show_count() 是类方法。
cls 表示 Student 类。
cls.count 表示访问类属性 count。
24. cls 是什么
cls 表示当前类。
它和 self 类似,但含义不同:
self 表示当前对象。
cls 表示当前类。
示例:
class Student:
school = "希望中学"
@classmethod
def show_school(cls):
print(cls.school)
Student.show_school()
输出:
希望中学
这里的 cls 就是 Student。
教学记忆:
self 管对象。
cls 管类。
25. 类方法适合做什么
类方法适合处理和类本身有关的逻辑。
常见用途:
访问或修改类属性
统计对象数量
提供另一种创建对象的方式
根据字符串、字典等数据创建对象
示例:根据字符串创建学生对象。
class Student:
def __init__(self, name, score):
self.name = name
self.score = score
@classmethod
def from_string(cls, text):
name, score = text.split(",")
return cls(name, int(score))
stu = Student.from_string("张三,90")
print(stu.name)
print(stu.score)
输出:
张三
90
这里:
return cls(name, int(score))
表示调用当前类来创建对象。
26. 为什么类方法中用 cls 而不是类名
看这个例子:
class Student:
def __init__(self, name):
self.name = name
@classmethod
def create(cls, name):
return cls(name)
如果以后有子类继承 Student,cls 可以代表调用这个方法的类。
虽然初学阶段不需要深入继承,但可以先记住:
类方法里推荐用 cls,而不是把类名写死。
不推荐:
return Student(name)
推荐:
return cls(name)
这样代码更灵活。
27. 静态方法
静态方法使用 @staticmethod 装饰器。
静态方法没有固定的第一个参数。
它既不自动接收 self,也不自动接收 cls。
示例:
class MathTool:
@staticmethod
def add(a, b):
return a + b
print(MathTool.add(3, 5))
输出:
8
静态方法可以理解为:
放在类里面的普通函数。
28. 静态方法适合做什么
静态方法适合放和类主题相关,但不需要访问对象和类的数据的工具逻辑。
例如:
class ScoreTool:
@staticmethod
def is_valid_score(score):
return 0 <= score <= 100
print(ScoreTool.is_valid_score(90))
print(ScoreTool.is_valid_score(150))
输出:
True
False
这里 is_valid_score() 和“成绩”这个主题有关。
但它不需要:
self
cls
实例属性
类属性
所以可以写成静态方法。
29. 三种方法对比
| 方法类型 | 装饰器 | 第一个参数 | 访问实例属性 | 访问类属性 | 常见用途 |
|---|---|---|---|---|---|
| 实例方法 | 无 | self |
可以 | 可以 | 操作对象数据 |
| 类方法 | @classmethod |
cls |
不直接操作具体对象 | 可以 | 操作类数据、创建对象 |
| 静态方法 | @staticmethod |
无 | 不自动访问 | 不自动访问 | 工具函数 |
示例:
class Demo:
class_value = 100
def __init__(self, value):
self.value = value
def instance_method(self):
print(self.value)
@classmethod
def class_method(cls):
print(cls.class_value)
@staticmethod
def static_method(a, b):
return a + b
教学顺序建议:
先讲实例方法。
再讲类方法。
最后讲静态方法。
30. 方法调用方式
实例方法通常通过对象调用。
stu.show_info()
类方法通常通过类调用。
Student.show_count()
静态方法通常通过类调用。
MathTool.add(3, 5)
完整示例:
class Demo:
def instance_method(self):
print("实例方法")
@classmethod
def class_method(cls):
print("类方法")
@staticmethod
def static_method():
print("静态方法")
demo = Demo()
demo.instance_method()
Demo.class_method()
Demo.static_method()
输出:
实例方法
类方法
静态方法
31. 类方法和静态方法也能通过对象调用吗
可以,但教学中建议通过类名调用。
class Demo:
@classmethod
def class_method(cls):
print("类方法")
@staticmethod
def static_method():
print("静态方法")
demo = Demo()
demo.class_method()
demo.static_method()
可以运行。
但是更推荐:
Demo.class_method()
Demo.static_method()
原因:
这样更清楚地表达:这些方法主要和类有关,不是某个具体对象的行为。
32. 什么是 @property
@property 是 Python 提供的一种装饰器。
它可以把一个方法变成像属性一样访问。
先看普通方法写法:
class Student:
def __init__(self, score):
self.__score = score
def get_score(self):
return self.__score
stu = Student(90)
print(stu.get_score())
输出:
90
使用 @property 后:
class Student:
def __init__(self, score):
self.__score = score
@property
def score(self):
return self.__score
stu = Student(90)
print(stu.score)
输出:
90
注意:
stu.score
看起来像访问属性,但背后实际调用了 score() 方法。
33. @property 的作用
@property 的作用可以总结为:
让方法像属性一样使用。
它的好处是:
外部使用简单。
内部仍然可以写逻辑。
可以控制属性读取。
可以控制属性修改。
可以做数据校验。
可以创建只读属性。
例如:
print(stu.score)
比:
print(stu.get_score())
更像普通属性访问。
但内部仍然可以保护真实数据。
34. 为什么需要 @property
先看直接公开属性:
class Student:
def __init__(self, score):
self.score = score
stu = Student(90)
stu.score = -100
print(stu.score)
输出:
-100
成绩变成负数,不合理。
如果使用普通 getter/setter:
class Student:
def __init__(self, score):
self.__score = 0
self.set_score(score)
def get_score(self):
return self.__score
def set_score(self, score):
if 0 <= score <= 100:
self.__score = score
else:
print("成绩不合法")
调用:
stu = Student(90)
stu.set_score(95)
print(stu.get_score())
可以控制数据,但用起来不像普通属性。
@property 可以兼顾两者:
外部像访问属性一样简单。
内部像方法一样可以控制逻辑。
35. @property 基本写法
class Student:
def __init__(self, score):
self.__score = score
@property
def score(self):
return self.__score
使用:
stu = Student(90)
print(stu.score)
注意:
定义时像方法。
使用时像属性。
定义时:
def score(self):
return self.__score
使用时:
stu.score
不要写:
stu.score()
因为加了 @property 后,它就应该像属性一样使用。
36. @property 创建只读属性
如果只定义 @property,不定义 setter,那么这个属性就是只读的。
示例:
class Circle:
def __init__(self, radius):
self.radius = radius
@property
def area(self):
return 3.14 * self.radius * self.radius
circle = Circle(5)
print(circle.area)
输出:
78.5
如果尝试修改:
circle.area = 100
会报错。
为什么 area 适合只读?
因为:
面积是根据半径计算出来的。
不应该直接手动修改面积。
37. 计算属性
通过 @property 定义的属性,可以是计算出来的。
例如矩形面积:
class Rectangle:
def __init__(self, width, height):
self.width = width
self.height = height
@property
def area(self):
return self.width * self.height
rect = Rectangle(3, 4)
print(rect.area)
rect.width = 10
print(rect.area)
输出:
12
40
这里:
area 没有直接保存。
每次访问 rect.area 时都会重新计算。
好处:
width 或 height 改变后,area 自动得到新结果。
38. @property 的 setter
如果希望属性可以被修改,并且修改时进行校验,可以定义 setter。
写法:
@属性名.setter
def 属性名(self, value):
...
示例:
class Student:
def __init__(self, score):
self.__score = 0
self.score = score
@property
def score(self):
return self.__score
@score.setter
def score(self, value):
if 0 <= value <= 100:
self.__score = value
else:
print("成绩必须在 0 到 100 之间")
stu = Student(90)
print(stu.score)
stu.score = 95
print(stu.score)
stu.score = 150
print(stu.score)
输出:
90
95
成绩必须在 0 到 100 之间
95
这里:
stu.score = 95
看起来是赋值,其实会自动调用 setter。
39. setter 的执行过程
看这段代码:
stu.score = 95
Python 会自动调用:
@score.setter
def score(self, value):
...
其中:
self 是 stu。
value 是 95。
所以 setter 中可以判断:
if 0 <= value <= 100:
self.__score = value
教学记忆:
读取属性走 @property。
修改属性走 @属性.setter。
40. @property 的 deleter
如果希望支持删除属性,可以定义 deleter。
写法:
@属性名.deleter
def 属性名(self):
...
示例:
class Student:
def __init__(self, score):
self.__score = score
@property
def score(self):
return self.__score
@score.deleter
def score(self):
print("删除成绩")
del self.__score
stu = Student(90)
print(stu.score)
del stu.score
输出:
90
删除成绩
执行:
del stu.score
会自动调用 deleter。
实际教学中,deleter 用得不如 getter 和 setter 多。
41. @property 完整结构
完整结构如下:
class 类名:
def __init__(self, value):
self.__value = value
@property
def value(self):
return self.__value
@value.setter
def value(self, new_value):
self.__value = new_value
@value.deleter
def value(self):
del self.__value
使用:
obj.value # 调用 getter
obj.value = 100 # 调用 setter
del obj.value # 调用 deleter
教学时重点掌握:
@property
@属性名.setter
deleter 可以作为了解内容。
42. @property 和私有属性配合
@property 经常和私有属性一起使用。
常见写法:
class Student:
def __init__(self, score):
self.__score = 0
self.score = score
@property
def score(self):
return self.__score
@score.setter
def score(self, value):
if 0 <= value <= 100:
self.__score = value
else:
print("成绩不合法")
外部访问:
stu.score
内部保存:
self.__score
好处:
外部不知道内部真实属性名。
外部不能直接乱改私有属性。
修改时可以统一校验。
43. 避免 property 无限递归
这是 @property 中非常常见的错误。
错误写法:
class Student:
@property
def score(self):
return self.score
为什么错?
因为:
访问 self.score 会再次调用 score 这个 property。
然后又访问 self.score。
一直重复,造成无限递归。
正确写法:
class Student:
def __init__(self, score):
self.__score = score
@property
def score(self):
return self.__score
教学记忆:
property 外面叫 score。
内部真实数据用 __score 或 _score 保存。
44. property 名称和真实属性名称
推荐命名:
class Student:
def __init__(self, score):
self.__score = score
@property
def score(self):
return self.__score
这里:
score 是给外部使用的属性名。
__score 是内部真实保存数据的属性名。
也有人使用单下划线:
class Student:
def __init__(self, score):
self._score = score
@property
def score(self):
return self._score
两种都常见。
教学中可以先使用双下划线,强调私有属性。
45. @property 与普通 getter/setter 对比
普通 getter/setter:
class Student:
def __init__(self, score):
self.__score = score
def get_score(self):
return self.__score
def set_score(self, score):
self.__score = score
调用:
stu.get_score()
stu.set_score(90)
@property 写法:
class Student:
def __init__(self, score):
self.__score = score
@property
def score(self):
return self.__score
@score.setter
def score(self, value):
self.__score = value
调用:
stu.score
stu.score = 90
对比:
| 写法 | 读取 | 修改 | 特点 |
|---|---|---|---|
| getter/setter | get_score() |
set_score(90) |
明确是方法 |
@property |
score |
score = 90 |
像属性一样自然 |
46. 什么时候使用 @property
适合使用 @property 的场景:
需要把方法伪装成属性
需要创建只读属性
需要在赋值时做数据校验
需要隐藏内部真实属性
需要计算属性
希望外部访问方式更简洁
示例:
成绩 score 需要校验 0 到 100
年龄 age 需要校验 0 到 150
面积 area 根据宽高计算
余额 balance 不希望外部随便改
不一定需要 @property 的场景:
只是普通公开数据,没有校验逻辑。
例如:
class Book:
def __init__(self, title):
self.title = title
如果 title 没有特殊限制,直接公开也可以。
47. @property 案例:年龄校验
class Person:
def __init__(self, name, age):
self.name = name
self.__age = 0
self.age = age
@property
def age(self):
return self.__age
@age.setter
def age(self, value):
if 0 <= value <= 150:
self.__age = value
else:
print("年龄必须在 0 到 150 之间")
person = Person("张三", 18)
print(person.age)
person.age = -5
print(person.age)
person.age = 20
print(person.age)
输出:
18
年龄必须在 0 到 150 之间
18
20
这里:
age 看起来是普通属性。
但赋值时会自动校验。
48. @property 案例:商品价格
class Product:
def __init__(self, name, price):
self.name = name
self.__price = 0
self.price = price
@property
def price(self):
return self.__price
@price.setter
def price(self, value):
if value >= 0:
self.__price = value
else:
print("价格不能小于 0")
product = Product("苹果", 5)
print(product.price)
product.price = -10
print(product.price)
输出:
5
价格不能小于 0
5
49. @property 案例:只读面积
class Rectangle:
def __init__(self, width, height):
self.width = width
self.height = height
@property
def area(self):
return self.width * self.height
@property
def perimeter(self):
return (self.width + self.height) * 2
rect = Rectangle(3, 4)
print(rect.area)
print(rect.perimeter)
rect.width = 10
print(rect.area)
print(rect.perimeter)
输出:
12
14
40
28
这里:
area 和 perimeter 是计算属性。
它们没有 setter,所以是只读属性。
50. @property 案例:温度转换
class Temperature:
def __init__(self, celsius):
self.__celsius = 0
self.celsius = celsius
@property
def celsius(self):
return self.__celsius
@celsius.setter
def celsius(self, value):
if value >= -273.15:
self.__celsius = value
else:
print("温度不能低于绝对零度")
@property
def fahrenheit(self):
return self.__celsius * 9 / 5 + 32
temp = Temperature(25)
print(temp.celsius)
print(temp.fahrenheit)
temp.celsius = 100
print(temp.fahrenheit)
输出:
25
77.0
212.0
这里:
celsius 可以读取和修改。
fahrenheit 根据 celsius 计算,是只读属性。
51. 实例方法、类方法、静态方法与 property 综合案例
class Student:
school = "希望中学"
count = 0
def __init__(self, name, score):
self.name = name
self.__score = 0
self.score = score
Student.count += 1
@property
def score(self):
return self.__score
@score.setter
def score(self, value):
if Student.is_valid_score(value):
self.__score = value
else:
print("成绩必须在 0 到 100 之间")
def show_info(self):
print(f"{self.name},学校:{Student.school},成绩:{self.score}")
def is_passed(self):
return self.score >= 60
@classmethod
def show_count(cls):
print(f"学生数量:{cls.count}")
@staticmethod
def is_valid_score(score):
return 0 <= score <= 100
stu1 = Student("张三", 90)
stu2 = Student("李四", 55)
stu1.show_info()
stu2.show_info()
print(stu1.is_passed())
print(stu2.is_passed())
stu2.score = 75
stu2.show_info()
Student.show_count()
输出:
张三,学校:希望中学,成绩:90
李四,学校:希望中学,成绩:55
True
False
李四,学校:希望中学,成绩:75
学生数量:2
这个案例中:
school 和 count 是类属性。
name 和 __score 是实例属性。
show_info() 和 is_passed() 是实例方法。
show_count() 是类方法。
is_valid_score() 是静态方法。
score 是 property 属性。
52. 常见错误 1:实例方法忘记写 self
错误写法:
class Student:
def show_info():
print("学生信息")
stu = Student()
stu.show_info()
会报错。
正确写法:
class Student:
def show_info(self):
print("学生信息")
记忆:
实例方法第一个参数写 self。
53. 常见错误 2:在实例方法中访问属性忘记 self
错误写法:
class Student:
def __init__(self, name):
self.name = name
def show_name(self):
print(name)
正确写法:
class Student:
def __init__(self, name):
self.name = name
def show_name(self):
print(self.name)
记忆:
访问实例属性要写 self.属性名。
54. 常见错误 3:把实例属性写成类属性
错误写法:
class Student:
name = ""
score = 0
如果 name 和 score 是每个学生不同的数据,就不应该写成类属性。
推荐:
class Student:
def __init__(self, name, score):
self.name = name
self.score = score
记忆:
每个对象不同的数据,写成实例属性。
55. 常见错误 4:可变类属性被共享
错误写法:
class Student:
hobbies = []
多个对象会共享同一个列表。
如果每个对象应该有自己的列表,应该写:
class Student:
def __init__(self):
self.hobbies = []
56. 常见错误 5:通过对象修改类属性
不推荐:
stu.school = "实验中学"
如果 school 是类属性,推荐:
Student.school = "实验中学"
通过对象赋值可能创建同名实例属性,导致学生误解。
57. 常见错误 6:类方法忘记 @classmethod
错误写法:
class Student:
count = 0
def show_count(cls):
print(cls.count)
Student.show_count()
可能会报错,因为 Python 不会自动把类传给 cls。
正确写法:
class Student:
count = 0
@classmethod
def show_count(cls):
print(cls.count)
58. 常见错误 7:静态方法里使用 self
错误写法:
class MathTool:
@staticmethod
def add(a, b):
print(self)
return a + b
静态方法不会自动接收 self。
如果需要访问实例属性,就不应该写成静态方法,而应该写成实例方法。
59. 常见错误 8:property 使用时加括号
错误写法:
print(stu.score())
如果 score 使用了 @property,应该像属性一样使用:
print(stu.score)
记忆:
property 定义时像方法,使用时像属性。
60. 常见错误 9:property 中返回自己导致递归
错误写法:
class Student:
@property
def score(self):
return self.score
正确写法:
class Student:
def __init__(self, score):
self.__score = score
@property
def score(self):
return self.__score
61. 常见错误 10:setter 名字不一致
错误写法:
class Student:
@property
def score(self):
return self.__score
@score.setter
def set_score(self, value):
self.__score = value
虽然语法可能不一定立刻按预期工作,但不推荐这样写。
推荐 getter 和 setter 使用同一个名字:
class Student:
@property
def score(self):
return self.__score
@score.setter
def score(self, value):
self.__score = value
记忆:
property 名和 setter 方法名保持一致。
62. 常见错误 11:只定义 getter 却尝试赋值
class Circle:
def __init__(self, radius):
self.radius = radius
@property
def area(self):
return 3.14 * self.radius * self.radius
circle = Circle(5)
circle.area = 100
会报错。
原因:
area 只有 getter,没有 setter,是只读属性。
63. 常见错误 12:过度使用 property
不是所有属性都要写成 @property。
如果属性没有校验逻辑,也不是计算属性,直接公开通常更简单。
例如:
class Book:
def __init__(self, title):
self.title = title
不一定非要写成:
@property
def title(self):
return self.__title
教学总结:
需要控制访问时用 property。
简单普通属性可以直接公开。
64. 注意事项 1:属性和方法命名要清楚
属性名通常使用名词:
name
age
score
price
stock
balance
方法名通常使用动词或动词短语:
show_info()
update_score()
calculate_area()
is_passed()
add_stock()
好的命名可以让学生一眼看出:
这是数据,还是行为。
65. 注意事项 2:不要让属性和方法同名
不推荐:
class Student:
def __init__(self):
self.score = 90
def score(self):
return 100
属性和方法同名会造成混乱。
推荐:
self.score = 90
def get_score(self):
...
或者:
@property
def score(self):
...
但不要同时用普通属性和普通方法占用同一个名字。
66. 注意事项 3:实例方法不要承担太多职责
一个方法最好只做一件主要事情。
推荐:
def update_score(self, score):
self.score = score
def is_passed(self):
return self.score >= 60
不推荐一个方法里同时:
修改成绩
打印信息
保存文件
发送通知
计算排名
教学记忆:
一个方法,一个主要任务。
67. 注意事项 4:类属性不要存放对象独有状态
对象独有状态应该用实例属性。
例如:
学生成绩
账户余额
商品库存
用户密码
这些都应该属于每个对象自己。
不应该写成类属性。
68. 注意事项 5:类方法适合处理类级别逻辑
类方法不是实例方法的替代品。
如果逻辑需要访问某个具体对象的数据,就应该使用实例方法。
适合类方法:
显示对象总数
修改全局类配置
从字符串创建对象
从字典创建对象
不适合类方法:
修改某个学生的成绩
显示某个学生的姓名
修改某个账户的余额
这些应该是实例方法。
69. 注意事项 6:静态方法不要强行使用
静态方法只是放在类里的普通函数。
如果一个函数和类没有明显关系,直接写成普通函数也可以。
适合静态方法:
成绩合法性检查
价格格式化
和类主题密切相关的小工具
不适合静态方法:
完全和类无关的工具函数
70. 注意事项 7:property 适合表现为“属性”的内容
适合 @property:
score
age
area
balance
price
不适合 @property:
save_to_file
send_email
update_database
download_data
这些明显是动作,应该写成普通方法。
教学判断:
像数据,用 property。
像动作,用方法。
71. 注意事项 8:setter 中要考虑边界值
例如成绩:
0 <= score <= 100
要明确:
0 是否合法?
100 是否合法?
-1 是否非法?
101 是否非法?
年龄:
0 <= age <= 150
价格:
price >= 0
库存:
stock >= 0
边界值是课堂练习的好材料。
72. 综合案例:银行账户
class BankAccount:
bank_name = "Python 银行"
count = 0
def __init__(self, owner, balance=0):
self.owner = owner
self.__balance = 0
self.balance = balance
BankAccount.count += 1
@property
def balance(self):
return self.__balance
@balance.setter
def balance(self, value):
if value >= 0:
self.__balance = value
else:
print("余额不能小于 0")
def deposit(self, money):
if money > 0:
self.__balance += money
else:
print("存款金额必须大于 0")
def withdraw(self, money):
if money <= 0:
print("取款金额必须大于 0")
elif money > self.__balance:
print("余额不足")
else:
self.__balance -= money
def show_info(self):
print(f"{self.owner},余额:{self.balance}")
@classmethod
def show_count(cls):
print(f"账户数量:{cls.count}")
@staticmethod
def is_valid_money(money):
return money > 0
account = BankAccount("张三", 1000)
account.deposit(500)
account.withdraw(300)
account.show_info()
account.balance = -100
account.show_info()
BankAccount.show_count()
print(BankAccount.is_valid_money(50))
输出:
张三,余额:1200
余额不能小于 0
张三,余额:1200
账户数量:1
True
这个案例中:
bank_name、count 是类属性。
owner、__balance 是实例属性。
balance 是 property。
deposit、withdraw、show_info 是实例方法。
show_count 是类方法。
is_valid_money 是静态方法。
73. 综合案例:商品类
class Product:
tax_rate = 0.13
def __init__(self, name, price, stock):
self.name = name
self.__price = 0
self.__stock = 0
self.price = price
self.stock = stock
@property
def price(self):
return self.__price
@price.setter
def price(self, value):
if value >= 0:
self.__price = value
else:
print("价格不能小于 0")
@property
def stock(self):
return self.__stock
@stock.setter
def stock(self, value):
if value >= 0:
self.__stock = value
else:
print("库存不能小于 0")
@property
def total_value(self):
return self.price * self.stock
def sell(self, count):
if count <= 0:
print("销售数量必须大于 0")
elif count > self.stock:
print("库存不足")
else:
self.stock -= count
def show_info(self):
print(f"{self.name},价格:{self.price},库存:{self.stock}")
@classmethod
def set_tax_rate(cls, rate):
if rate >= 0:
cls.tax_rate = rate
else:
print("税率不能小于 0")
@staticmethod
def is_valid_count(count):
return count > 0
product = Product("苹果", 5, 100)
product.show_info()
product.sell(3)
product.show_info()
print(product.total_value)
Product.set_tax_rate(0.1)
print(Product.tax_rate)
输出:
苹果,价格:5,库存:100
苹果,价格:5,库存:97
485
0.1
74. 课堂练习 1:实例属性和实例方法
要求:
- 定义
Student类。 - 在
__init__中定义name和score。 - 定义实例方法
show_info()。 - 创建对象并调用方法。
参考答案:
class Student:
def __init__(self, name, score):
self.name = name
self.score = score
def show_info(self):
print(f"{self.name}: {self.score}")
stu = Student("张三", 90)
stu.show_info()
75. 课堂练习 2:类属性
要求:
- 定义
Student类。 - 添加类属性
school。 - 创建两个学生对象。
- 输出它们的学校名称。
参考答案:
class Student:
school = "希望中学"
def __init__(self, name):
self.name = name
stu1 = Student("张三")
stu2 = Student("李四")
print(stu1.school)
print(stu2.school)
print(Student.school)
76. 课堂练习 3:统计对象数量
要求:
- 定义类属性
count。 - 每创建一个对象,
count加 1。 - 输出创建的对象数量。
参考答案:
class Student:
count = 0
def __init__(self, name):
self.name = name
Student.count += 1
stu1 = Student("张三")
stu2 = Student("李四")
print(Student.count)
77. 课堂练习 4:类方法
要求:
- 定义
Student类。 - 类属性
count记录学生数量。 - 定义类方法
show_count()显示学生数量。
参考答案:
class Student:
count = 0
def __init__(self, name):
self.name = name
Student.count += 1
@classmethod
def show_count(cls):
print(f"学生数量:{cls.count}")
stu1 = Student("张三")
stu2 = Student("李四")
Student.show_count()
78. 课堂练习 5:静态方法
要求:
- 定义
ScoreTool类。 - 定义静态方法
is_valid_score(score)。 - 判断成绩是否在
0到100之间。
参考答案:
class ScoreTool:
@staticmethod
def is_valid_score(score):
return 0 <= score <= 100
print(ScoreTool.is_valid_score(90))
print(ScoreTool.is_valid_score(150))
79. 课堂练习 6:只读 property
要求:
- 定义
Circle类。 - 有
radius属性。 - 使用
@property定义只读属性area。
参考答案:
class Circle:
def __init__(self, radius):
self.radius = radius
@property
def area(self):
return 3.14 * self.radius * self.radius
circle = Circle(5)
print(circle.area)
80. 课堂练习 7:带 setter 的 property
要求:
- 定义
Person类。 - 使用私有属性保存年龄。
- 使用
@property读取年龄。 - 使用 setter 修改年龄,并校验年龄在
0到150之间。
参考答案:
class Person:
def __init__(self, age):
self.__age = 0
self.age = age
@property
def age(self):
return self.__age
@age.setter
def age(self, value):
if 0 <= value <= 150:
self.__age = value
else:
print("年龄不合法")
person = Person(18)
print(person.age)
person.age = -5
print(person.age)
81. 课堂练习 8:判断输出
阅读代码,判断输出:
class Student:
school = "希望中学"
def __init__(self, name):
self.name = name
stu1 = Student("张三")
stu2 = Student("李四")
stu1.school = "实验中学"
print(stu1.school)
print(stu2.school)
print(Student.school)
答案:
实验中学
希望中学
希望中学
解释:
stu1.school = "实验中学" 创建了 stu1 自己的实例属性。
没有修改 Student.school。
82. 课堂练习 9:找错误
下面代码有什么问题?
class Student:
@property
def score(self):
return self.score
答案:
return self.score 会再次调用 score 这个 property,导致无限递归。
正确写法:
class Student:
def __init__(self, score):
self.__score = score
@property
def score(self):
return self.__score
83. 课堂练习 10:综合练习
要求:
定义一个 Product 类:
- 类属性
tax_rate = 0.13。 - 实例属性
name。 - 使用
@property封装price,价格不能小于0。 - 实例方法
show_info()显示商品信息。 - 类方法
set_tax_rate()修改税率。 - 静态方法
is_valid_price()判断价格是否合法。
参考答案:
class Product:
tax_rate = 0.13
def __init__(self, name, price):
self.name = name
self.__price = 0
self.price = price
@property
def price(self):
return self.__price
@price.setter
def price(self, value):
if Product.is_valid_price(value):
self.__price = value
else:
print("价格不能小于 0")
def show_info(self):
print(f"{self.name},价格:{self.price},税率:{Product.tax_rate}")
@classmethod
def set_tax_rate(cls, rate):
if rate >= 0:
cls.tax_rate = rate
else:
print("税率不能小于 0")
@staticmethod
def is_valid_price(price):
return price >= 0
product = Product("苹果", 5)
product.show_info()
product.price = -10
product.show_info()
Product.set_tax_rate(0.1)
product.show_info()
84. 教学总结
Python 类中的属性、方法和 @property 是面向对象编程的重要基础。
一句话总结:
属性保存数据,方法表示行为,property 让方法像属性一样使用。
核心知识:
- 实例属性属于具体对象。
- 类属性属于类,被所有对象共享。
- 每个对象不同的数据,用实例属性。
- 所有对象共享的数据,用类属性。
- 实例方法第一个参数是
self。 self表示当前对象。- 类方法使用
@classmethod。 - 类方法第一个参数是
cls。 cls表示当前类。- 静态方法使用
@staticmethod。 - 静态方法不自动接收
self或cls。 @property可以让方法像属性一样访问。@属性.setter可以控制属性赋值。- 只定义 getter 的 property 是只读属性。
- property 内部不要返回自己,避免无限递归。
常用模板:
class 类名:
类属性 = 值
def __init__(self, 参数):
self.实例属性 = 参数
def 实例方法(self):
pass
@classmethod
def 类方法(cls):
pass
@staticmethod
def 静态方法():
pass
property 模板:
class 类名:
def __init__(self, value):
self.__value = 0
self.value = value
@property
def value(self):
return self.__value
@value.setter
def value(self, new_value):
if 条件:
self.__value = new_value
else:
print("数据不合法")
课堂记忆口诀:
对象自己的,实例属性。
大家共享的,类属性。
self 管对象,
cls 管类。
实例方法最常用,
类方法管类数据,
静态方法像工具。
property 像属性,
setter 做校验。
最后记住:
属性、方法和 property 的目标,
不是让代码变复杂,
而是让数据更清楚、行为更明确、使用更自然。