10. Python 面向对象程序设计

Python 面向对象程序设计(OOP)

基本概念

1. 类(Class)

  • 对象的"蓝图",定义属性和方法
  • 命名规范:大驼峰 ClassName

2. 对象(Object)

  • 类的实例,具体的数据+行为
1
2
3
4
5
class Dog:                      # 类
pass

my_dog = Dog() # 创建对象(实例)
print(type(my_dog)) # <class '__main__.Dog'>

属性

1. 类属性(Class Attribute)

  • 所有实例共享的数据
  • 类名访问:ClassName.attr

2. 实例属性(Instance Attribute)

  • 每个实例独立的数据
  • 通常在 __init__ 中定义,用 self. 访问
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Dog:
species = "Canis familiaris" # 类属性(所有狗都是这个物种)

def __init__(self, name, age): # 初始化函数
self.name = name # 实例属性
self.age = age # 实例属性

# 使用
print(Dog.species) # Canis familiaris(类访问类属性)
dog1 = Dog("Buddy", 3)
dog2 = Dog("Max", 5)
print(dog1.name) # Buddy(实例访问实例属性)
print(dog2.name) # Max
print(dog1.species) # Canis familiaris(实例可访问类属性)

# 修改类属性影响所有实例
Dog.species = "Canis lupus"
print(dog1.species) # Canis lupus
print(dog2.species) # Canis lupus

方法

1. 初始化函数 __init__

  • 构造方法,创建对象时自动调用
  • 第一个参数必须是 self(实例本身)

2. 实例方法(Instance Method)

  • 第一个参数 self,操作实例数据
  • 通过 实例.方法() 调用

3. 类方法 @classmethod

  • 第一个参数 cls(类本身)
  • 可访问/修改类属性,创建替代构造器

4. 静态方法 @staticmethod

  • self/cls,与类/实例无关
  • 逻辑上属于类的工具函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
from datetime import date

class Dog:
species = "Canis familiaris"
count = 0 # 统计实例个数

def __init__(self, name, age):
self.name = name
self.age = age
Dog.count += 1 # 修改类属性

# 实例方法
def bark(self):
return f"{self.name} says: 汪汪!"

def get_info(self):
return f"{self.name} is {self.age} years old"

# 类方法
@classmethod
def get_species(cls):
return cls.species

@classmethod
def from_birth_year(cls, name, birth_year): # 替代构造器
age = date.today().year - birth_year
return cls(name, age)

# 静态方法
@staticmethod
def is_dog_year(age): # 狗龄转人类年龄
return age * 7

@staticmethod
def check_adult(age):
return age >= 3

# 使用
dog = Dog("Buddy", 3)
print(dog.bark()) # Buddy says: 汪汪!
print(dog.get_info()) # Buddy is 3 years old

print(Dog.get_species()) # Canis familiaris(类方法)
print(dog.get_species()) # 也可实例调用

# 类方法创建对象
dog2 = Dog.from_birth_year("Max", 2020) # 自动算年龄

# 静态方法(不需要实例)
print(Dog.is_dog_year(3)) # 21
print(Dog.check_adult(5)) # True
print(Dog.count) # 2(创建了两个实例)

封装

核心思想

  • 隐藏内部实现,暴露必要接口
  • 防止外部随意修改内部状态

1. 私有属性(Private Attribute)

命名 含义 实际效果
_单下划线 约定私有,外部可访问但不建议 提示"内部使用"
__双下划线 名称修饰(name mangling),外部难直接访问 变成 _类名__属性

2. 私有方法(Private Method)

  • 同样用单/双下划线
  • 内部辅助逻辑,不暴露给外部

3. @property 装饰器

  • 把方法伪装成属性访问
  • 实现 getter/setter,控制读写
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
class BankAccount:
def __init__(self, owner, balance):
self.owner = owner
self._bank_code = "ICBC001" # 单下划线:约定私有
self.__balance = balance # 双下划线:名称修饰保护

# 私有方法(双下划线)
def __validate(self, amount):
return amount > 0

# 对外接口:安全存取
def deposit(self, amount):
if self.__validate(amount):
self.__balance += amount
return True
return False

def withdraw(self, amount):
if self.__validate(amount) and amount <= self.__balance:
self.__balance -= amount
return True
return False

# @property 让方法像属性一样访问
@property
def balance(self): # getter
return self.__balance

@balance.setter
def balance(self, value): # setter(控制写入)
raise AttributeError("不能直接修改余额,请使用 deposit/withdraw")

# 使用
acc = BankAccount("Tom", 1000)

# 访问"属性"(实际调用方法)
print(acc.balance) # 1000(看起来像属性,实则是 @property)

# 试图直接修改
# acc.balance = 2000 # AttributeError: 不能直接修改余额

# 必须通过接口操作
acc.deposit(500)
print(acc.balance) # 1500

# 双下划线"伪私有"(实际可绕过,但不建议)
print(acc._BankAccount__balance) # 1500(名称修饰后的真名)

# 单下划线只是约定
print(acc._bank_code) # ICBC001(完全可访问,但别这么干)

继承

核心概念

  • 父类(基类):被继承的类
  • 子类(派生类):继承父类,可扩展/重写
  • 方法重写(Override):子类重新定义父类方法

关键函数

  • super():调用父类方法,避免硬编码父类名
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
class Animal:                           # 父类
def __init__(self, name, age):
self.name = name
self.age = age

def speak(self): # 父类方法(占位)
raise NotImplementedError("子类必须实现")

def info(self):
return f"{self.name}, {self.age}岁"

class Dog(Animal): # 子类继承 Animal
def __init__(self, name, age, breed):
super().__init__(name, age) # 调用父类构造
self.breed = breed # 新增属性

def speak(self): # 重写父类方法
return f"{self.name}: 汪汪!"

def fetch(self): # 子类特有方法
return f"{self.name} 去捡球了"

class Cat(Animal): # 另一个子类
def speak(self):
return f"{self.name}: 喵喵!"

# 使用
dog = Dog("Buddy", 3, "Golden Retriever")
cat = Cat("Kitty", 2)

print(dog.info()) # Buddy, 3岁(继承父类方法)
print(dog.speak()) # Buddy: 汪汪!(重写)
print(dog.fetch()) # Buddy 去捡球了(子类特有)

print(cat.speak()) # Kitty: 喵喵!(各自重写)

# 多态:同一接口,不同表现
animals = [dog, cat]
for a in animals:
print(a.speak()) # 自动调用各自版本

多重继承(Python 特色)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Flyable:
def fly(self):
return "会飞"

class Swimmable:
def swim(self):
return "会游泳"

class Duck(Animal, Flyable, Swimmable): # 继承多个类
def speak(self):
return "嘎嘎!"

donald = Duck("Donald", 5)
print(donald.fly()) # 会飞
print(donald.swim()) # 会游泳

多态(Polymorphism)

核心思想

  • 同一接口,不同实现
  • 不关心对象具体类型,只关心有没有该方法
  • Python 是鸭子类型:“如果它走起来像鸭子,叫起来像鸭子,那它就是鸭子”
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Dog:
def speak(self):
return "汪汪!"

class Cat:
def speak(self):
return "喵喵!"

class Duck:
def speak(self):
return "嘎嘎!"

# 多态函数:不判断类型,只调 speak()
def animal_concert(animals):
for animal in animals:
print(animal.speak()) # 各自发出不同声音

# 使用
zoo = [Dog(), Cat(), Duck()]
animal_concert(zoo)
# 汪汪!
# 喵喵!
# 嘎嘎!

抽象基类(规范接口)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
from abc import ABC, abstractmethod

class Animal(ABC): # 抽象基类
@abstractmethod
def speak(self): # 子类必须实现
pass

@abstractmethod
def move(self):
pass

class Dog(Animal):
def speak(self): # 必须实现
return "汪汪!"

def move(self): # 必须实现
return "四条腿跑"

# animal = Animal() # 报错:不能实例化抽象类
dog = Dog() # 必须实现所有抽象方法才能实例化

综合实战:完整类设计

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
from datetime import date
from abc import ABC, abstractmethod

# 抽象基类
class Employee(ABC):
__count = 0 # 类属性:私有,统计员工数

def __init__(self, name, hire_year):
self.name = name
self._hire_year = hire_year
Employee.__count += 1

@property
def years_of_service(self):
return date.today().year - self._hire_year

@abstractmethod
def calculate_salary(self): # 抽象方法:子类必须实现
pass

@classmethod
def get_employee_count(cls):
return cls.__count

def __str__(self): # 重写,友好打印
return f"{self.name}{self.years_of_service}年工龄)"

# 子类:正式员工
class FullTimeEmployee(Employee):
def __init__(self, name, hire_year, base_salary, bonus_rate):
super().__init__(name, hire_year)
self.__base = base_salary # 私有
self.__bonus_rate = bonus_rate

@property
def base_salary(self): # 受控访问
return self.__base

@base_salary.setter
def base_salary(self, value):
if value > 0:
self.__base = value

def calculate_salary(self): # 实现抽象方法
return self.__base * (1 + self.__bonus_rate)

# 子类:合同工
class ContractEmployee(Employee):
def __init__(self, name, hire_year, hourly_rate, hours):
super().__init__(name, hire_year)
self.hourly_rate = hourly_rate
self.hours = hours

def calculate_salary(self):
return self.hourly_rate * self.hours

# 使用
emp1 = FullTimeEmployee("张三", 2018, 10000, 0.2)
emp2 = ContractEmployee("李四", 2020, 100, 160)

print(Employee.get_employee_count()) # 2

employees = [emp1, emp2]
for e in employees:
print(f"{e}: {e.calculate_salary():.2f}元")
# 张三(7年工龄): 12000.00元
# 李四(5年工龄): 16000.00元

快速记忆口诀

特性 口诀
类 vs 对象 类是图纸,对象是房子
属性 类属性大家共享,实例属性各管各
方法 实例用 self,类用 cls,静态啥不用
封装 单下划线提醒,双下划线保护,@property 伪装
继承 super() 找爸爸,重写覆盖,扩展新功能
多态 鸭子类型,同一接口,不同表现,不管你是谁

考试习题

1. 快乐的数字
编写一个算法来确定一个数字是否“快乐”。 快乐的数字按照如下方式确定:从一个正整数开始,用其每位数的平方之和取代该数,并重复这个过程,直到最后数字要么收敛等于 1 且一直等于 1,要么将无休止地循环下去且最终不会收敛等于 1。能够最终收敛等于 1 的数就是快乐的数字。

例如: 19 就是一个快乐的数字,计算过程如下:

1 ** 2 + 9 ** 2 = 82
8 ** 2 + 2 ** 2 = 68
6 ** 2 + 8 ** 2 = 100
1 ** 2 + 0 ** 2 + 0 ** 2 = 1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
def func(a):
s = 0
while(a >0 ):
rest = a % 10
s = s + rest * rest
a = a // 10
return s

def main():
inputStr = input()
try:
integer = eval(inputStr)
except:
print(False)
else:
i = 0
while( i < 1000 and integer != 1):
integer = func(integer)
i += 1
if i < 1000 and integer == 1:
print(True)
else:
print(False)

main()

2. 阶乘累计求和

获得用户输入的整数n,输出 1!+2!+…+n! 的值。 如果输入数值为 0、负数、非数字或非整数,输出提示信息:输入有误,请输入正整数。

1 = 1
1*2 = 2 3
1*2*3 = 6 9
1*2*3*4 = 24 33
1*2*3*4*5 = 120 153

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
def func(length):
item = 1
summer = 0
for i in range(1, length+1):
item *= i
summer += item
return summer

def main():
try:
inputStr = input()
integer = eval(inputStr)
if (integer <= 0):
raise Exception
print(func(integer))
except:
print("输入有误,请输入正整数")
return

main()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
def main():
USER_NAME = 'Kate'
PWD = '12345'
LEN = 3
for i in range(LEN):
userName = input()
pwd = input()
if USER_NAME == userName and PWD == pwd:
print("登录成功!")
break
if i + 1 == LEN:
print("3 次用户名或者密码均有误!退出程序。")
else:
print("用户名或者密码错误!")

main()