Python编程基础(八) | 类

发布于:2025-09-09 ⋅ 阅读:(21) ⋅ 点赞:(0)

引言:很久没有写 Python 了,有一点生疏。这是学习《Python 编程:从入门到实践(第3版)》的课后练习记录,主要目的是快速回顾基础知识。

练习1:餐馆

创建一个名为Restaurant的类,为其__init__()方法设置两个属性:restaurant_namecuisine_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_namecuisine_type 属性,互不影响。

练习3:用户

创建一个名为User的类,其中包含属性first_namelast_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__ 方法中为属性赋一个初始值,这样在创建实例时就无需为它提供实参。
  • 修改属性值
    1. 直接修改:通过实例直接访问并赋新值(如 restaurant.number_served = 5)。简单直接,但不够安全。
    2. 通过方法修改:编写专门的方法(如 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_attemptsreset_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 a User),而组合是 “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.pyPrivilegesAdmin 放入 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() vs Die(sides=10)),可以用同一个类创建出行为略有不同的对象。

练习14 & 15:彩票

  1. 从一个包含数字和字母的列表中随机抽取4个,组成中奖号码。
  2. 编写一个循环,模拟不断抽奖,直到抽中预设的中奖号码为止,并计算次数。
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)来追踪循环执行的次数是一种常见的编程模式。
  • 程序模拟:通过编程来模拟现实世界中的随机事件(如彩票),是检验概率和算法的有效方法。

网站公告

今日签到

点亮在社区的每一天
去签到