引言:很久没有写 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_name
self.cuisine_type = cuisine_type
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.")
# 创建一个类的实例
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_name
self.cuisine_type = cuisine_type
def 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_name
self.last_name = last_name
self.age = age
self.city = city
def 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_name
self.cuisine_type = cuisine_type
self.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 = number
def increment_number_served(self, increment):
"""将就餐人数按指定的数量增加。"""
self.number_served += increment
restaurant = 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_name
self.last_name = last_name
self.login_attempts = 0
def greet_user(self):
print(f"Hello, {self.first_name} {self.last_name}")
def increment_login_attempts(self):
"""将登录尝试次数增加 1。"""
self.login_attempts += 1
def reset_login_attempts(self):
"""将登录尝试次数重置为 0。"""
self.login_attempts = 0
user1 = 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_name
self.cuisine_type = cuisine_type
def 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_name
self.last_name = last_name
def 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_name
self.last_name = last_name
def 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 = make
self.model = model
self.year = year
self.odometer_reading = 0
# ...
class Battery:
"""一次模拟电动汽车电瓶的简单尝试"""
def __init__(self, battery_size=40):
self.battery_size = battery_size
def describe_battery(self):
print(f"This car has a {self.battery_size}-kWh battery.")
def get_range(self):
"""根据电池容量打印续航里程。"""
if self.battery_size == 40:
range = 150
elif self.battery_size == 65:
range = 225
else:
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 = 65
class 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 Restaurant
my_restaurant = Restaurant('The Pizza Place', 'Pizza')
my_restaurant.describe_restaurant()
文件 1: user.py
class User:
# ... User 类的完整定义 ...
文件 2: admin.py
from user import User
class Privileges:
# ... Privileges 类的完整定义 ...
class Admin(User):
# ... Admin 类的完整定义 ...
主文件: main.py
from admin import Admin
the_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 randint
class Die:
def __init__(self, sides=6):
"""初始化骰子的面数。"""
self.sides = sides
def 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 choice
def 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
)来追踪循环执行的次数是一种常见的编程模式。 - 程序模拟:通过编程来模拟现实世界中的随机事件(如彩票),是检验概率和算法的有效方法。