Python编程基础(八) | 类
引言:很久没有写 Python 了,有一点生疏。这是学习《Python 编程:从入门到实践(第3版)》的课后练习记录,主要目的是快速回顾基础知识。
练习1:餐馆
创建一个名为
Restaurant
的类,为其__init__()
方法设置两个属性:restaurant_name
和cuisine_type
。创建一个名为describe_restaurant()
的方法和一个名为open_restaurant()
的方法,其中前者打印前述两项信息,而后者打印一条消息,指出餐馆正在营业。根据这个类创建一个名为
restaurant
的实例,分别打印其两个属性,再调用前述两个方法。
class Restaurant:def __init__(self, restaurant_name, cuisine_type):"""初始化餐馆名和菜品类型属性。"""self.restaurant_name = restaurant_nameself.cuisine_type = cuisine_typedef describe_restaurant(self):"""打印餐馆的描述信息。"""print(f"Restaurant name: {self.restaurant_name}")print(f"Cuisine type: {self.cuisine_type}")def open_restaurant(self):"""打印餐馆正在营业的消息。"""print(f"{self.restaurant_name} is open.")# 创建一个类的实例
restaurant = Restaurant("乡村基", "中餐")# 调用实例的方法
restaurant.describe_restaurant()
restaurant.open_restaurant()
Restaurant name: 乡村基
Cuisine type: 中餐
乡村基 is open.
知识点回顾:
- 类的定义:使用
class
关键字定义一个类,类名通常采用驼峰命名法(Restaurant
)。 - 构造方法
__init__()
:这是一个特殊的方法,在创建类的新实例时会自动调用。它的第一个参数必须是self
。 self
参数:它是一个指向实例本身的引用,让实例能够访问类中的属性和方法。- 属性 (Attribute):通过
self.属性名 = 值
的方式在__init__()
方法中定义,用于存储与实例相关的数据。 - 方法 (Method):在类中定义的函数。每个方法都必须包含
self
这个参数。 - 实例化:通过
类名(实参)
的方式(如Restaurant("乡村基", "中餐")
)创建一个类的实例(或称对象)。 - 访问属性和方法:使用点号 (
.
) 来访问实例的属性(如restaurant.restaurant_name
)或调用其方法(如restaurant.describe_restaurant()
)。
练习2:三家餐馆
根据为练习1编写的类创建三个实例,并对每个实例调用
describe_restaurant()
方法。
# (此处省略了 Restaurant 类的重复定义)
class Restaurant:def __init__(self, restaurant_name, cuisine_type):self.restaurant_name = restaurant_nameself.cuisine_type = cuisine_typedef describe_restaurant(self):print(f"\nRestaurant name: {self.restaurant_name}")print(f"Cuisine type: {self.cuisine_type}")restaurant1 = Restaurant("乡村基", "中餐")
restaurant1.describe_restaurant()restaurant2 = Restaurant("喜茶", "奶茶")
restaurant2.describe_restaurant()restaurant3 = Restaurant("绵阳米粉", "中餐")
restaurant3.describe_restaurant()
Restaurant name: 乡村基
Cuisine type: 中餐Restaurant name: 喜茶
Cuisine type: 奶茶Restaurant name: 绵阳米粉
Cuisine type: 中餐
知识点回顾:
- 创建多个实例:一个类就像一个蓝图,可以根据它创建任意数量的独立实例。
- 实例的独立性:
restaurant1
,restaurant2
, 和restaurant3
是三个完全独立的对象。它们各自拥有自己的restaurant_name
和cuisine_type
属性,互不影响。
练习3:用户
创建一个名为
User
的类,其中包含属性first_name
和last_name
,还有用户简介中通常会有的其他几个属性。在类User
中定义一个名为describe_user()
的方法,用于打印用户信息摘要。再定义一个名为greet_user()
的方法,用于向用户发出个性化的问候。创建多个表示不同用户的实例,并对每个实例调用上述两个方法。
class User:def __init__(self, first_name, last_name, age, city):"""初始化用户属性。"""self.first_name = first_nameself.last_name = last_nameself.age = ageself.city = citydef describe_user(self):"""打印用户信息的摘要。"""print(f"\nUser Profile:")print(f"- First name: {self.first_name}")print(f"- Last name: {self.last_name}")print(f"- Age: {self.age}")print(f"- City: {self.city}")def greet_user(self):"""向用户发出个性化的问候。"""full_name = f"{self.first_name} {self.last_name}"print(f"Hello, {full_name}!")user1 = User("Alice", "Smith", 30, "New York")
user1.describe_user()
user1.greet_user()user2 = User("Bob", "Johnson", 25, "Los Angeles")
user2.describe_user()
user2.greet_user()
User Profile:
- First name: Alice
- Last name: Smith
- Age: 30
- City: New York
Hello, Alice Smith!User Profile:
- First name: Bob
- Last name: Johnson
- Age: 25
- City: Los Angeles
Hello, Bob Johnson!
知识点回顾:
- 类的封装:将相关的数据(属性)和操作这些数据的代码(方法)捆绑在一个对象中,是对现实世界实体(如“用户”)的抽象。这使得代码结构更清晰,更易于管理。
练习4:就餐人数
在为练习1编写的程序中,添加一个名为
number_served
的属性,并将其默认值设置为0。
- 添加一个名为
set_number_served()
的方法,用来设置就餐人数。- 添加一个名为
increment_number_served()
的方法,用来让就餐人数递增。
class Restaurant:def __init__(self, restaurant_name, cuisine_type):self.restaurant_name = restaurant_nameself.cuisine_type = cuisine_typeself.number_served = 0 # 设置默认值def describe_restaurant(self):print(f"Restaurant name: {self.restaurant_name}")print(f"Cuisine type: {self.cuisine_type}")def open_restaurant(self):print(f"{self.restaurant_name} is open.")def set_number_served(self, number):"""设置就餐人数。"""self.number_served = numberdef increment_number_served(self, increment):"""将就餐人数按指定的数量增加。"""self.number_served += incrementrestaurant = Restaurant("乡村基", "中餐")
print(f"初始就餐人数: {restaurant.number_served}")# 修改属性值
restaurant.number_served = 5
print(f"直接修改后的就餐人数: {restaurant.number_served}")# 通过方法设置
restaurant.set_number_served(20)
print(f"通过 set_number_served() 设置后的就餐人数: {restaurant.number_served}")# 通过方法递增
restaurant.increment_number_served(50)
print(f"通过 increment_number_served() 增加后的就餐人数: {restaurant.number_served}")
初始就餐人数: 0
直接修改后的就餐人数: 5
通过 set_number_served() 设置后的就餐人数: 20
通过 increment_number_served() 增加后的就餐人数: 70
知识点回顾:
- 属性的默认值:可以直接在
__init__
方法中为属性赋一个初始值,这样在创建实例时就无需为它提供实参。 - 修改属性值:
- 直接修改:通过实例直接访问并赋新值(如
restaurant.number_served = 5
)。简单直接,但不够安全。 - 通过方法修改:编写专门的方法(如
set_number_served()
)来更新属性值。这是更推荐的方式,因为它允许在方法内部添加检查逻辑(如检查新值是否有效),从而更好地控制属性。
- 直接修改:通过实例直接访问并赋新值(如
- 递增属性值:编写方法(如
increment_number_served()
)来对属性进行增量修改,而不是完全替换。这使得对属性的操作更加具体和清晰。
练习5:尝试登录次数
在
User
类中,添加一个login_attempts
属性。编写一个increment_login_attempts()
方法,将该值加1。再编写一个reset_login_attempts()
方法,将其重置为0。
class User:def __init__(self, first_name, last_name):self.first_name = first_nameself.last_name = last_nameself.login_attempts = 0def greet_user(self):print(f"Hello, {self.first_name} {self.last_name}")def increment_login_attempts(self):"""将登录尝试次数增加 1。"""self.login_attempts += 1def reset_login_attempts(self):"""将登录尝试次数重置为 0。"""self.login_attempts = 0user1 = User("Alice", "Smith")
print(f"初始登录次数: {user1.login_attempts}")user1.increment_login_attempts()
user1.increment_login_attempts()
user1.increment_login_attempts()
print(f"递增后登录次数: {user1.login_attempts}")user1.reset_login_attempts()
print(f"重置后登录次数: {user1.login_attempts}")
初始登录次数: 0
递增后登录次数: 3
重置后登录次数: 0
知识点回顾:
- 管理对象状态:通过为类编写专门的方法(如
increment_login_attempts
和reset_login_attempts
),可以更好地管理和控制实例(对象)的内部状态(属性login_attempts
),使对象的行为更加明确和可预测。
练习6:冰激凌小店
编写一个名为
IceCreamStand
的类,让它继承Restaurant
类。添加一个名为flavors
的属性,用于存储一个口味列表。编写一个显示这些口味的方法。
class Restaurant:def __init__(self, restaurant_name, cuisine_type):self.restaurant_name = restaurant_nameself.cuisine_type = cuisine_typedef describe_restaurant(self):print(f"Restaurant name: {self.restaurant_name.title()}")print(f"Cuisine type: {self.cuisine_type}")class IceCreamStand(Restaurant):"""冰激凌小店是一种特殊的餐馆。"""def __init__(self, restaurant_name, cuisine_type='ice cream'):"""初始化父类的属性,并添加冰激凌口味属性。"""super().__init__(restaurant_name, cuisine_type)self.flavors = ["chocolate", "vanilla", "strawberry"]def display_flavors(self):"""打印所有可用的口味。"""print("Available flavors:")for flavor in self.flavors:print(f"- {flavor.title()}")ice_cream_shop = IceCreamStand("DQ")
ice_cream_shop.describe_restaurant()
ice_cream_shop.display_flavors()
Restaurant name: Dq
Cuisine type: ice cream
Available flavors:
- Chocolate
- Vanilla
- Strawberry
知识点回顾:
- 继承 (Inheritance):一个类(子类)可以继承另一个类(父类)的属性和方法。
- 父类与子类:
Restaurant
是父类,IceCreamStand
是子类。子类自动获得父类的所有非私有属性和方法。 super()
函数:super().__init__(...)
用于调用父类的__init__
方法,确保子类实例也能正确初始化从父类继承的属性。- 代码复用:继承是实现代码复用的强大方式。子类可以直接使用父类的方法(如
describe_restaurant()
),无需重写。 - 添加子类特有功能:子类可以在继承父类的基础上,定义自己独有的属性(
flavors
)和方法(display_flavors()
)。
练习7:管理员
编写一个名为
Admin
的类,让它继承User
类。添加一个名为privileges
的属性,用来存储一个权限字符串列表。编写一个show_privileges()
方法,显示管理员的权限。
class User:def __init__(self, first_name, last_name):self.first_name = first_nameself.last_name = last_namedef greet_user(self):full_name = f"{self.first_name} {self.last_name}"print(f"Hello, {full_name.title()}!")class Admin(User):def __init__(self, first_name, last_name):"""初始化父类属性,并添加管理员权限属性。"""super().__init__(first_name, last_name)self.privileges = ['can add post', 'can delete post', 'can ban user']def show_privileges(self):"""显示管理员拥有的权限。"""print(f"Admin {self.first_name.title()} has the following privileges:")for privilege in self.privileges:print(f"- {privilege}")admin = Admin(first_name='John', last_name='Doe')
admin.greet_user()
admin.show_privileges()
Hello, John Doe!
Admin John has the following privileges:
- can add post
- can delete post
- can ban user
知识点回顾:
- 继承的应用:这个练习再次展示了继承。
Admin
“是一种”User
,它拥有User
的所有特性(名和姓),同时还具备自己独特的特性(权限列表)。
练习8:权限
编写一个名为
Privileges
的类,它只有一个属性privileges
。将方法show_privileges()
移到这个类中。在Admin
类中,将一个Privileges
实例用作其属性。
class User:# (同上一个练习,此处省略)def __init__(self, first_name, last_name):self.first_name = first_nameself.last_name = last_namedef greet_user(self):full_name = f"{self.first_name} {self.last_name}"print(f"Hello, {full_name.title()}!")class Privileges:"""一个专门用于表示和显示管理员权限的类。"""def __init__(self):self.privileges = ['can add post', 'can delete post', 'can ban user']def show_privileges(self):"""显示权限列表。"""print('The admin has the following privileges:')for privilege in self.privileges:print(f"- {privilege}")class Admin(User):def __init__(self, first_name, last_name):super().__init__(first_name, last_name)self.privileges = Privileges() # 将 Privileges 实例作为属性admin = Admin(first_name='John', last_name='Doe')
admin.greet_user()
admin.privileges.show_privileges() # 注意调用方式的变化
Hello, John Doe!
The admin has the following privileges:
- can add post
- can delete post
- can ban user
知识点回顾:
- 组合 (Composition):将一个类的实例作为另一个类的属性。这体现了 “has-a”(有一个)关系,例如
Admin
“有一个”Privileges
对象。 - 继承 vs. 组合:继承是 “is-a” 关系(
Admin
is aUser
),而组合是 “has-a” 关系。当一个类需要另一个类的功能,但逻辑上不属于其子类时,组合是更好的选择。 - 代码解耦:通过将权限管理逻辑封装到独立的
Privileges
类中,Admin
类的职责更单一,代码结构更清晰,也更易于维护和扩展。
练习9:电池升级
给
Battery
类添加一个upgrade_battery()
方法。这个方法检查电池容量,如果不是65,就设置为65。创建一辆电动汽车,调用get_range()
,然后升级电池,并再次调用get_range()
。
class Car:"""一次模拟汽车的简单尝试"""# (Car 类的代码此处省略)def __init__(self, make, model, year):self.make = makeself.model = modelself.year = yearself.odometer_reading = 0# ...class Battery:"""一次模拟电动汽车电瓶的简单尝试"""def __init__(self, battery_size=40):self.battery_size = battery_sizedef describe_battery(self):print(f"This car has a {self.battery_size}-kWh battery.")def get_range(self):"""根据电池容量打印续航里程。"""if self.battery_size == 40:range = 150elif self.battery_size == 65:range = 225else:range = "unknown"print(f"This car can go about {range} miles on a full charge.")def upgrade_battery(self):"""检查电瓶容量,并尝试升级到65kWh。"""if self.battery_size < 65:print("Upgrading the battery...")self.battery_size = 65class ElectricCar(Car):"""电动汽车的独特之处"""def __init__(self, make, model, year):super().__init__(make, model, year)self.battery = Battery()my_leaf = ElectricCar(make='nissan', model='leaf', year=2024)
my_leaf.battery.get_range()
my_leaf.battery.upgrade_battery()
my_leaf.battery.get_range()
This car can go about 150 miles on a full charge.
Upgrading the battery...
This car can go about 225 miles on a full charge.
知识点回顾:
- 组合的实际应用:
ElectricCar
类本身不处理电池逻辑,而是包含一个Battery
实例,并将所有与电池相关的操作(如get_range
,upgrade_battery
)委托给这个Battery
对象。这使得ElectricCar
类的代码更简洁。 - 在类的方法中添加逻辑:
upgrade_battery()
方法内部包含了if
条件判断,这使得对象的方法可以根据自身状态(self.battery_size
)执行不同的操作。
练习10 & 11 & 12:模块化
将类存储在单独的文件(模块)中,然后在主程序文件中导入并使用它们。
- 练习10: 将
Restaurant
类放入restaurant.py
。- 练习11: 将
User
,Privileges
,Admin
类放入user_admin.py
。- 练习12: 将
User
放入user.py
,Privileges
和Admin
放入admin.py
。
文件 1: restaurant.py
class Restaurant:# ... Restaurant 类的完整定义 ...
主文件: main.py
from restaurant import Restaurantmy_restaurant = Restaurant('The Pizza Place', 'Pizza')
my_restaurant.describe_restaurant()
文件 1: user.py
class User:# ... User 类的完整定义 ...
文件 2: admin.py
from user import Userclass Privileges:# ... Privileges 类的完整定义 ...class Admin(User):# ... Admin 类的完整定义 ...
主文件: main.py
from admin import Adminthe_admin = Admin('super', 'user')
the_admin.privileges.show_privileges()
知识点回顾:
- 模块 (Module):每个
.py
文件就是一个模块。将类和函数存储在模块中可以使项目结构更有条理。 import
语句:from module_name import ClassName
:从一个模块中导入一个或多个特定的类。import module_name
:导入整个模块,使用时需要module_name.ClassName
。
- 代码组织:将相关的类放在同一个模块中,不相关的类分开放置。当一个模块依赖另一个模块中的类时(如
admin.py
依赖user.py
),可以在模块内部进行导入。这极大地提高了代码的可维护性和重用性。
练习13:骰子
创建一个
Die
类,有一个属性sides
默认为6。编写一个roll_die()
方法,打印一个1到sides
之间的随机数。创建不同面数的骰子并掷多次。
from random import randintclass Die:def __init__(self, sides=6):"""初始化骰子的面数。"""self.sides = sidesdef roll_die(self):"""模拟掷骰子,并打印结果。"""result = randint(1, self.sides)print(f'Rolling a {self.sides}-sided die... Result: {result}')# 创建一个6面的骰子并掷10次
d6 = Die()
print("--- Rolling a 6-sided die 10 times ---")
for _ in range(10):d6.roll_die()# 创建一个10面的骰子并掷10次
d10 = Die(sides=10)
print("\n--- Rolling a 10-sided die 10 times ---")
for _ in range(10):d10.roll_die()
--- Rolling a 6-sided die 10 times ---
Rolling a 6-sided die... Result: 3
Rolling a 6-sided die... Result: 5
... (and so on)--- Rolling a 10-sided die 10 times ---
Rolling a 10-sided die... Result: 8
Rolling a 10-sided die... Result: 2
... (and so on)
知识点回顾:
- 使用标准库:通过
from random import randint
导入并使用 Python 标准库中random
模块的功能。 - 类与循环结合:在
for
循环中调用对象的方法,可以方便地重复执行某个动作。 - 灵活的实例:通过在创建实例时传递不同的参数(
Die()
vsDie(sides=10)
),可以用同一个类创建出行为略有不同的对象。
练习14 & 15:彩票
- 从一个包含数字和字母的列表中随机抽取4个,组成中奖号码。
- 编写一个循环,模拟不断抽奖,直到抽中预设的中奖号码为止,并计算次数。
from random import choicedef generate_ticket(pool):"""从奖池中随机抽取4个字符组成一张彩票。"""ticket = ""for _ in range(4):ticket += str(choice(pool))return ticket# 奖池
lottery_pool = ['0', '1','2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e']# 设定中奖号码
winning_ticket = generate_ticket(lottery_pool)
print(f'The winning ticket is: {winning_ticket}')# 开始循环抽奖,直到中奖
my_ticket = ""
attempts = 0
while my_ticket != winning_ticket:my_ticket = generate_ticket(lottery_pool)attempts += 1# 为了避免输出过多,可以每隔一定次数打印一次if attempts % 10000 == 0:print(f"Attempt #{attempts}: Drew ticket {my_ticket}...")print(f"\nCongratulations! You won after {attempts} attempts!")
print(f"Your winning ticket was: {my_ticket}")
The winning ticket is: 38c8
Attempt #10000: Drew ticket 7d77...
Attempt #20000: Drew ticket 4b29...
Attempt #30000: Drew ticket a986...Congratulations! You won after 30387 attempts!
Your winning ticket was: 38c8
知识点回顾:
random.choice()
:从一个非空序列(如列表)中随机返回一个元素。while
循环:当需要重复执行代码直到某个条件不再满足时(这里是my_ticket != winning_ticket
),while
循环非常适用。- 计数器变量:在循环中使用一个变量(
attempts
)来追踪循环执行的次数是一种常见的编程模式。 - 程序模拟:通过编程来模拟现实世界中的随机事件(如彩票),是检验概率和算法的有效方法。