Objectives 学习目标

  • To appreciate how defining new classes can provide structure for a complex program 理解定义新类如何为复杂程序提供结构
  • To be able to read and write Python class definitions 能够阅读和编写Python类定义
  • To understand the concept of encapsulation and how it contributes to building modular and maintainable programs 理解封装概念及其如何帮助构建模块化和可维护的程序
  • To be able to write programs involving simple class definitions 能够编写涉及简单类定义的程序
  • To be able to write interactive graphics programs involving novel (programmer designed) widgets 能够编写涉及新颖(程序员设计)小部件的交互式图形程序

Quick Review of Objects 对象快速回顾

Object Concept 对象概念

# 对象:知道事情并能做事情的活跃数据类型
class Circle:
    def __init__(self, center, radius):
        self.center = center    # 实例变量 - 知道的事情
        self.radius = radius    # 实例变量 - 知道的事情
    
    def draw(self):            # 方法 - 能做的事情
        print(f"Drawing circle at {self.center} with radius {self.radius}")
    
    def move(self, dx, dy):    # 方法 - 能做的事情
        self.center = (self.center[0] + dx, self.center[1] + dy)
        print(f"Moved circle to {self.center}")
 
# 创建对象实例
myCircle = Circle((0, 0), 20)  # 构造函数
myCircle.draw()                # 调用方法
myCircle.move(10, 10.5)        # 调用方法

Object Attributes 对象属性

class Student:
    def __init__(self, name, hours, qpoints):
        self.name = name        # 实例变量
        self.hours = float(hours)  # 实例变量
        self.qpoints = float(qpoints)  # 实例变量
    
    def getName(self):          # 方法
        return self.name
    
    def getHours(self):         # 方法
        return self.hours
    
    def getQPoints(self):       # 方法
        return self.qpoints
    
    def gpa(self):             # 方法
        return self.qpoints / self.hours if self.hours > 0 else 0.0
 
# 使用对象
student1 = Student("John Doe", 127, 228)
print(f"Name: {student1.getName()}")
print(f"GPA: {student1.gpa():.2f}")

Code Reuse and Modular Design 代码重用与模块化设计

Levels of Abstraction 抽象层次

# 层次1:函数 - 代码级抽象
def calculate_area(radius):
    """计算圆面积"""
    return 3.14159 * radius ** 2
 
# 层次2:对象 - 数据和功能的进一步抽象
class Circle:
    def __init__(self, radius):
        self.radius = radius
        self.area = self.calculate_area()
    
    def calculate_area(self):
        return 3.14159 * self.radius ** 2
    
    def get_area(self):
        return self.area
 
# 使用比较
radius = 5
area_function = calculate_area(radius)  # 函数方式
circle_object = Circle(radius)          # 对象方式
area_object = circle_object.get_area()
 
print(f"函数计算面积: {area_function}")
print(f"对象计算面积: {area_object}")

Cannonball Program Specification 炮弹程序规范

Problem Analysis 问题分析

# 输入参数
# - 发射角度(度)
# - 初始速度(米/秒)  
# - 初始高度(米)
# 输出:炮弹在击中地面前行进的距离(米)
 
def cannonball_simulation():
    """
    炮弹飞行模拟 - 面向过程设计
    """
    # 获取输入
    angle = float(input("发射角度(度): "))
    velocity = float(input("初始速度(米/秒): "))
    height = float(input("初始高度(米): "))
    
    # 计算初始位置和速度分量
    xpos = 0.0
    ypos = height
    
    # 角度转弧度
    import math
    theta = math.radians(angle)
    xvel = velocity * math.cos(theta)
    yvel = velocity * math.sin(theta)
    
    # 时间间隔
    time_interval = 0.1
    
    # 模拟循环
    while ypos >= 0.0:
        # 更新位置和速度
        xpos = xpos + time_interval * xvel
        yvel1 = yvel - time_interval * 9.8  # 重力加速度
        ypos = ypos + time_interval * (yvel + yvel1) / 2.0
        yvel = yvel1
        
        print(f"位置: ({xpos:.2f}, {ypos:.2f})")
    
    print(f"\n炮弹飞行距离: {xpos:.2f} 米")
    return xpos
 
# 运行模拟
# cannonball_simulation()

Modular Process-Oriented Design 模块化面向过程设计

import math
 
def get_inputs():
    """获取用户输入"""
    angle = float(input("发射角度(度): "))
    velocity = float(input("初始速度(米/秒): "))
    height = float(input("初始高度(米): "))
    time_interval = 0.1
    return angle, velocity, height, time_interval
 
def get_xy_components(velocity, angle):
    """计算速度的x和y分量"""
    theta = math.radians(angle)
    xvel = velocity * math.cos(theta)
    yvel = velocity * math.sin(theta)
    return xvel, yvel
 
def update_cannonball(time, xpos, ypos, xvel, yvel):
    """更新炮弹位置和速度"""
    yvel1 = yvel - time * 9.8
    ypos = ypos + time * (yvel + yvel1) / 2.0
    xpos = xpos + time * xvel
    return xpos, ypos, yvel1
 
def main_process():
    """主程序 - 面向过程"""
    angle, velocity, height, time = get_inputs()
    xpos, ypos = 0.0, height
    xvel, yvel = get_xy_components(velocity, angle)
    
    while ypos >= 0:
        xpos, ypos, yvel = update_cannonball(time, xpos, ypos, xvel, yvel)
    
    print(f"\n飞行距离: {xpos:.1f} 米")
 
# 问题:参数太多,代码不够清晰

Object-Oriented Design 面向对象设计

Projectile Class 炮弹类

import math
 
class Projectile:
    """
    炮弹类 - 面向对象设计
    封装炮弹的物理属性和行为
    """
    
    def __init__(self, angle, velocity, height):
        """构造函数 - 初始化炮弹"""
        # 位置分量
        self.xpos = 0.0
        self.ypos = height
        
        # 速度分量
        theta = math.radians(angle)
        self.xvel = velocity * math.cos(theta)
        self.yvel = velocity * math.sin(theta)
        
        # 物理常量
        self.gravity = 9.8
    
    def update(self, time):
        """更新炮弹位置 - 经过time秒后"""
        # 更新x位置(忽略空气阻力)
        self.xpos = self.xpos + time * self.xvel
        
        # 计算新的y速度
        yvel1 = self.yvel - time * self.gravity
        
        # 更新y位置(使用平均速度)
        self.ypos = self.ypos + time * (self.yvel + yvel1) / 2.0
        
        # 更新y速度
        self.yvel = yvel1
    
    def get_x(self):
        """获取x坐标"""
        return self.xpos
    
    def get_y(self):
        """获取y坐标"""
        return self.ypos
    
    def is_above_ground(self):
        """检查炮弹是否还在空中"""
        return self.ypos >= 0
 
def main_oo():
    """主程序 - 面向对象版本"""
    # 获取输入
    angle = float(input("发射角度(度): "))
    velocity = float(input("初始速度(米/秒): "))
    height = float(input("初始高度(米): "))
    
    # 创建炮弹对象
    cannonball = Projectile(angle, velocity, height)
    time_interval = 0.1
    
    # 模拟循环
    while cannonball.is_above_ground():
        cannonball.update(time_interval)
        print(f"位置: ({cannonball.get_x():.2f}, {cannonball.get_y():.2f})")
    
    print(f"\n炮弹飞行距离: {cannonball.get_x():.2f} 米")
 
# 优势:代码更清晰,逻辑更明确

Example: Multi-Sided Dice 示例:多面骰子

MSDie Class Implementation MSDie类实现

import random
 
class MSDie:
    """
    多面骰子类
    """
    
    def __init__(self, sides):
        """构造函数 - 初始化骰子"""
        self.sides = sides      # 骰子面数
        self.value = 1          # 当前值
        self.roll_history = []  # 投掷历史
    
    def roll(self):
        """投掷骰子 - 随机设置值"""
        self.value = random.randint(1, self.sides)
        self.roll_history.append(self.value)
        return self.value
    
    def set_value(self, value):
        """设置骰子值(作弊功能)"""
        if 1 <= value <= self.sides:
            self.value = value
            self.roll_history.append(f"set:{value}")
        else:
            print(f"错误:值必须在1到{self.sides}之间")
    
    def get_value(self):
        """获取当前值"""
        return self.value
    
    def get_sides(self):
        """获取面数"""
        return self.sides
    
    def get_history(self):
        """获取投掷历史"""
        return self.roll_history
    
    def __str__(self):
        """字符串表示"""
        return f"MSDie({self.sides}): {self.value}"
    
    def __repr__(self):
        """详细字符串表示"""
        return f"MSDie(sides={self.sides}, value={self.value}, history={self.roll_history})"
 
# 测试骰子类
def test_dice():
    print("=== 多面骰子测试 ===")
    
    # 创建6面骰子
    die1 = MSDie(6)
    print(f"初始值: {die1.get_value()}")
    
    # 投掷几次
    for i in range(3):
        die1.roll()
        print(f"第{i+1}次投掷: {die1.get_value()}")
    
    # 设置特定值
    die1.set_value(4)
    print(f"设置后的值: {die1.get_value()}")
    
    # 查看历史
    print(f"投掷历史: {die1.get_history()}")
    
    # 创建13面骰子
    die2 = MSDie(13)
    die2.roll()
    print(f"\n13面骰子: {die2.get_value()}")
    
    # 测试字符串表示
    print(f"骰子1: {die1}")
    print(f"骰子2: {repr(die2)}")
 
# test_dice()

Data Processing with Class 使用类进行数据处理

Student Record Class 学生记录类

class Student:
    """
    学生类 - 用于数据处理
    """
    
    def __init__(self, name, hours, qpoints):
        """构造函数"""
        self.name = name
        self.hours = float(hours)
        self.qpoints = float(qpoints)
    
    def getName(self):
        """获取姓名"""
        return self.name
    
    def getHours(self):
        """获取学时"""
        return self.hours
    
    def getQPoints(self):
        """获取绩点"""
        return self.qpoints
    
    def gpa(self):
        """计算GPA"""
        return self.qpoints / self.hours if self.hours > 0 else 0.0
    
    def __str__(self):
        """字符串表示"""
        return f"{self.name}: {self.hours}学时, GPA={self.gpa():.2f}"
 
def find_best_student(filename):
    """
    从数据文件中找到GPA最高的学生
    """
    try:
        with open(filename, 'r') as file:
            best_student = None
            
            for line in file:
                # 解析每行数据
                data = line.strip().split()
                if len(data) >= 3:
                    # 组合姓名(可能包含空格)
                    name = ' '.join(data[:-2])
                    hours = data[-2]
                    qpoints = data[-1]
                    
                    # 创建学生对象
                    student = Student(name, hours, qpoints)
                    
                    # 更新最佳学生
                    if best_student is None or student.gpa() > best_student.gpa():
                        best_student = student
            
            return best_student
            
    except FileNotFoundError:
        print(f"错误:文件 {filename} 未找到")
        return None
 
def create_sample_data():
    """创建示例数据文件"""
    sample_data = """Adams, Henry 127 228
Comptewell, Susan 100 400
DibbleBit, Denny 18 41.5
Jones, Jim 48.5 155
Smith, Frank 37 125.33"""
    
    with open('students.txt', 'w') as f:
        f.write(sample_data)
 
def process_student_data():
    """处理学生数据"""
    # 创建示例数据
    create_sample_data()
    
    # 查找最佳学生
    best = find_best_student('students.txt')
    
    if best:
        print("=== 最佳学生 ===")
        print(f"姓名: {best.getName()}")
        print(f"学时: {best.getHours()}")
        print(f"绩点: {best.getQPoints()}")
        print(f"GPA: {best.gpa():.3f}")
    else:
        print("未找到学生数据")
 
# process_student_data()

Modules and Namespaces 模块与命名空间

Module Creation and Usage 模块创建与使用

# projectile.py 模块文件
"""
projectile.py
炮弹物理模拟模块
提供Projectile类用于模拟抛射物运动
"""
 
import math
 
class Projectile:
    """抛射物类 - 模拟物体在重力下的运动"""
    
    def __init__(self, angle, velocity, height):
        self.xpos = 0.0
        self.ypos = height
        theta = math.radians(angle)
        self.xvel = velocity * math.cos(theta)
        self.yvel = velocity * math.sin(theta)
        self.gravity = 9.8
    
    def update(self, time):
        self.xpos += time * self.xvel
        yvel1 = self.yvel - time * self.gravity
        self.ypos += time * (self.yvel + yvel1) / 2.0
        self.yvel = yvel1
    
    def getX(self): return self.xpos
    def getY(self): return self.ypos
 
# 模块测试代码
if __name__ == "__main__":
    # 测试代码
    p = Projectile(45, 50, 0)
    print("模块测试通过")
 
# main_program.py 主程序文件
"""
主程序 - 使用projectile模块
"""
 
# 方法1:导入整个模块
import projectile
 
def main():
    ball = projectile.Projectile(45, 50, 10)
    print(f"初始位置: ({ball.getX()}, {ball.getY()})")
 
# 方法2:导入特定类
from projectile import Projectile
 
def main2():
    ball = Projectile(45, 50, 10)
    print(f"初始位置: ({ball.getX()}, {ball.getY()})")
 
# 方法3:导入所有(不推荐)
# from projectile import *
 
# 查看模块文档
# help(projectile)

Package Structure 包结构

"""
包示例结构:
physics/
    __init__.py          # 包初始化文件
    projectile.py        # 抛射物模块
    vectors.py           # 向量模块
    constants.py         # 物理常量模块
graphics/
    __init__.py
    widgets.py
    animations.py
main.py                 # 主程序
"""
 
# physics/__init__.py
"""
physics 包
物理模拟相关模块
"""
 
from .projectile import Projectile
from .vectors import Vector2D
 
__all__ = ['Projectile', 'Vector2D']
 
# 使用包
# from physics import Projectile, Vector2D
# 或
# import physics.projectile

Encapsulation and Abstraction 封装与抽象

Encapsulation Benefits 封装优势

class BankAccount:
    """
    银行账户类 - 展示封装优势
    """
    
    def __init__(self, account_holder, initial_balance=0):
        self.account_holder = account_holder
        self._balance = initial_balance  # 受保护变量
        self._transaction_history = []   # 内部实现细节
    
    def deposit(self, amount):
        """存款 - 公开接口"""
        if amount > 0:
            self._balance += amount
            self._transaction_history.append(f"存款: +${amount}")
            return True
        return False
    
    def withdraw(self, amount):
        """取款 - 公开接口"""
        if 0 < amount <= self._balance:
            self._balance -= amount
            self._transaction_history.append(f"取款: -${amount}")
            return True
        return False
    
    def get_balance(self):
        """获取余额 - 公开接口"""
        return self._balance
    
    def get_statement(self):
        """获取对账单 - 不暴露内部数据结构"""
        statement = f"账户持有人: {self.account_holder}\n"
        statement += f"当前余额: ${self._balance:.2f}\n"
        statement += "最近交易:\n"
        for transaction in self._transaction_history[-5:]:  # 只显示最近5笔
            statement += f"  {transaction}\n"
        return statement
    
    # 不提供直接访问_transaction_history的方法
    # 保护内部实现细节
 
# 使用封装类
account = BankAccount("张三", 1000)
account.deposit(500)
account.withdraw(200)
print(account.get_statement())
 
# 优势:
# 1. 内部实现可以改变而不影响使用者
# 2. 数据验证和业务逻辑集中管理
# 3. 使用更简单,不需要了解内部细节

Widgets and GUI Programming 小部件与GUI编程

Button Class 按钮类

from graphics import *
 
class Button:
    """
    按钮类 - 自定义GUI小部件
    """
    
    def __init__(self, win, center, width, height, label):
        """初始化按钮"""
        self.win = win
        self.center = center
        self.width = width
        self.height = height
        self.label = label
        self.active = False
        
        # 创建按钮视觉元素
        x, y = center.getX(), center.getY()
        w, h = width/2, height/2
        
        # 按钮矩形
        self.rect = Rectangle(Point(x-w, y-h), Point(x+w, y+h))
        self.rect.setFill('lightgray')
        self.rect.draw(win)
        
        # 按钮标签
        self.text = Text(center, label)
        self.text.draw(win)
        
        # 初始状态
        self.deactivate()
    
    def activate(self):
        """激活按钮"""
        self.active = True
        self.rect.setFill('lightgreen')
        self.text.setStyle('bold')
    
    def deactivate(self):
        """停用按钮"""
        self.active = False
        self.rect.setFill('lightgray')
        self.text.setStyle('normal')
    
    def clicked(self, p):
        """检查点击"""
        if not self.active:
            return False
        
        x, y = self.center.getX(), self.center.getY()
        w, h = self.width/2, self.height/2
        
        # 检查点是否在按钮区域内
        return (x-w <= p.getX() <= x+w and 
                y-h <= p.getY() <= y+h)
    
    def getLabel(self):
        """获取标签"""
        return self.label
    
    def setLabel(self, new_label):
        """设置新标签"""
        self.label = new_label
        self.text.setText(new_label)

DieView Class 骰子视图类

from graphics import *
 
class DieView:
    """
    骰子视图类 - 显示骰子面
    """
    
    def __init__(self, win, center, size):
        """初始化骰子视图"""
        self.win = win
        self.center = center
        self.size = size
        self.value = 1
        
        # 创建骰子正方形
        half = size/2
        x, y = center.getX(), center.getY()
        self.background = Rectangle(Point(x-half, y-half), 
                                   Point(x+half, y+half))
        self.background.setFill('white')
        self.background.setOutline('black')
        self.background.draw(win)
        
        # 创建所有可能的点(pip)
        self.pips = []
        self.__create_pips()
        
        # 设置初始值
        self.setValue(1)
    
    def __create_pips(self):
        """创建骰子上的点"""
        # 点的相对位置(百分比坐标)
        positions = [
            [(0.5, 0.5)],                           # 1
            [(0.25, 0.25), (0.75, 0.75)],           # 2  
            [(0.25, 0.25), (0.5, 0.5), (0.75, 0.75)], # 3
            [(0.25, 0.25), (0.25, 0.75), 
             (0.75, 0.25), (0.75, 0.75)],           # 4
            [(0.25, 0.25), (0.25, 0.75), (0.5, 0.5),
             (0.75, 0.25), (0.75, 0.75)],           # 5
            [(0.25, 0.25), (0.25, 0.5), (0.25, 0.75),
             (0.75, 0.25), (0.75, 0.5), (0.75, 0.75)] # 6
        ]
        
        # 创建点对象
        for pip_positions in positions:
            pip_group = []
            for pos in pip_positions:
                x = self.center.getX() + (pos[0] - 0.5) * self.size * 0.6
                y = self.center.getY() + (pos[1] - 0.5) * self.size * 0.6
                pip = Circle(Point(x, y), self.size * 0.1)
                pip.setFill('black')
                pip_group.append(pip)
            self.pips.append(pip_group)
    
    def setValue(self, value):
        """设置骰子值"""
        if 1 <= value <= 6:
            self.value = value
            self.__update_display()
    
    def __update_display(self):
        """更新显示"""
        # 隐藏所有点
        for pip_group in self.pips:
            for pip in pip_group:
                pip.undraw()
        
        # 显示当前值的点
        for pip in self.pips[self.value - 1]:
            pip.draw(self.win)
    
    def getValue(self):
        """获取当前值"""
        return self.value

Dice Roller Application 骰子滚动器应用

Complete Dice Roller 完整骰子滚动器

from graphics import *
from random import randint
 
def dice_roller():
    """骰子滚动器主程序"""
    # 创建窗口
    win = GraphWin("骰子滚动器", 400, 300)
    win.setBackground("white")
    
    # 创建骰子
    die1 = DieView(win, Point(150, 150), 60)
    die2 = DieView(win, Point(250, 150), 60)
    
    # 创建按钮
    roll_button = Button(win, Point(150, 250), 80, 30, "滚动")
    quit_button = Button(win, Point(250, 250), 80, 30, "退出")
    
    # 激活滚动按钮,停用退出按钮
    roll_button.activate()
    quit_button.deactivate()
    
    # 第一次滚动
    die1.setValue(randint(1, 6))
    die2.setValue(randint(1, 6))
    quit_button.activate()  # 至少滚动一次后才能退出
    
    # 事件循环
    while True:
        click = win.getMouse()
        
        if roll_button.clicked(click):
            # 滚动骰子
            die1.setValue(randint(1, 6))
            die2.setValue(randint(1, 6))
            quit_button.activate()
            
        elif quit_button.clicked(click):
            break
    
    # 清理
    win.close()
 
# 注意:需要先实现Button和DieView类
# dice_roller()

Animated Cannonball 动画炮弹

ShotTracker Class 炮弹轨迹类

from graphics import *
from projectile import Projectile
 
class ShotTracker:
    """
    炮弹轨迹跟踪器 - 结合物理模拟和图形显示
    """
    
    def __init__(self, win, angle, velocity, height):
        """初始化炮弹跟踪器"""
        self.win = win
        
        # 物理模拟部分
        self.proj = Projectile(angle, velocity, height)
        
        # 图形显示部分
        self.marker = Circle(Point(0, 0), 3)
        self.marker.setFill("red")
        self.marker.setOutline("red")
        self.marker.draw(win)
        
        # 更新初始位置
        self.update(0)
    
    def update(self, dt):
        """更新炮弹位置"""
        # 更新物理模拟
        self.proj.update(dt)
        
        # 更新图形位置
        x = self.proj.getX()
        y = self.win.getHeight() - self.proj.getY()  # 转换坐标系
        
        # 移动标记
        center = self.marker.getCenter()
        dx = x - center.getX()
        dy = y - center.getY()
        self.marker.move(dx, dy)
    
    def getX(self):
        """获取x坐标"""
        return self.proj.getX()
    
    def getY(self):
        """获取y坐标"""
        return self.proj.getY()
    
    def is_above_ground(self):
        """检查是否在地面以上"""
        return self.proj.getY() >= 0
    
    def undraw(self):
        """移除图形"""
        self.marker.undraw()

InputDialog Class 输入对话框类

class InputDialog:
    """
    输入对话框类 - 获取用户输入
    """
    
    def __init__(self, angle, vel, height):
        """初始化对话框"""
        self.win = GraphWin("发射参数", 300, 200)
        self.win.setBackground("lightblue")
        
        # 创建输入框和标签
        Text(Point(50, 30), "角度:").draw(self.win)
        self.angle_entry = Entry(Point(150, 30), 10)
        self.angle_entry.setText(str(angle))
        self.angle_entry.draw(self.win)
        
        Text(Point(50, 60), "速度:").draw(self.win)
        self.vel_entry = Entry(Point(150, 60), 10)
        self.vel_entry.setText(str(vel))
        self.vel_entry.draw(self.win)
        
        Text(Point(50, 90), "高度:").draw(self.win)
        self.height_entry = Entry(Point(150, 90), 10)
        self.height_entry.setText(str(height))
        self.height_entry.draw(self.win)
        
        # 创建按钮
        self.fire_button = Button(self.win, Point(100, 140), 60, 25, "发射")
        self.quit_button = Button(self.win, Point(200, 140), 60, 25, "退出")
        self.fire_button.activate()
        self.quit_button.activate()
    
    def interact(self):
        """与用户交互"""
        while True:
            click = self.win.getMouse()
            
            if self.quit_button.clicked(click):
                self.win.close()
                return None, None, None, "Quit"
            
            if self.fire_button.clicked(click):
                try:
                    angle = float(self.angle_entry.getText())
                    vel = float(self.vel_entry.getText())
                    height = float(self.height_entry.getText())
                    self.win.close()
                    return angle, vel, height, "Fire"
                except ValueError:
                    # 输入无效,继续等待
                    pass
    
    def close(self):
        """关闭对话框"""
        self.win.close()

Complete Animation 完整动画程序

def animated_cannonball():
    """动画炮弹主程序"""
    # 创建主窗口
    win = GraphWin("炮弹模拟", 640, 480, autoflush=False)
    win.setCoords(-10, -10, 210, 155)
    win.setBackground("lightblue")
    
    # 绘制地面
    ground = Line(Point(-10, 0), Point(210, 0))
    ground.setWidth(3)
    ground.draw(win)
    
    # 初始参数
    angle, vel, height = 45.0, 40.0, 2.0
    
    while True:
        # 获取输入
        input_dialog = InputDialog(angle, vel, height)
        result = input_dialog.interact()
        
        if result[3] == "Quit":
            break
        
        angle, vel, height, _ = result
        
        # 创建炮弹
        shot = ShotTracker(win, angle, vel, height)
        
        # 动画循环
        dt = 1.0 / 30  # 30帧每秒
        while shot.is_above_ground() and shot.getX() < 210:
            shot.update(dt)
            update(30)  # 限制帧率
        
        # 等待点击继续
        if shot.is_above_ground():
            Text(Point(100, 140), "炮弹飞出边界!").draw(win)
        else:
            Text(Point(100, 140), f"距离: {shot.getX():.1f}").draw(win)
        
        win.getMouse()
        # 清理文本
        for item in win.items[:]:
            if isinstance(item, Text):
                item.undraw()
        
        shot.undraw()
    
    win.close()
 
# 注意:需要完整的图形库支持
# animated_cannonball()

Class Attributes and Methods 类属性与方法

Class Attributes 类属性

class Student:
    """
    学生类 - 演示类属性
    """
    
    # 类属性 - 所有实例共享
    school_name = "Python University"
    total_students = 0
    gpa_scale = 4.0
    
    def __init__(self, name, hours, qpoints):
        """实例构造函数"""
        self.name = name
        self.hours = float(hours)
        self.qpoints = float(qpoints)
        
        # 更新类属性
        Student.total_students += 1
        self.student_id = Student.total_students
    
    def gpa(self):
        """实例方法"""
        return self.qpoints / self.hours if self.hours > 0 else 0.0
    
    @classmethod
    def get_school_info(cls):
        """类方法 - 操作类属性"""
        return f"{cls.school_name} - 总学生数: {cls.total_students}"
    
    @classmethod
    def set_gpa_scale(cls, new_scale):
        """类方法 - 修改类属性"""
        cls.gpa_scale = new_scale
        return f"GPA标准已更新为: {cls.gpa_scale}"
    
    @classmethod
    def create_from_string(cls, student_string):
        """类方法 - 替代构造函数"""
        # 格式: "姓名,学时,质量点"
        name, hours, qpoints = student_string.split(',')
        return cls(name.strip(), hours.strip(), qpoints.strip())
    
    @staticmethod
    def calculate_gpa(hours, qpoints):
        """静态方法 - 不依赖于实例或类"""
        return qpoints / hours if hours > 0 else 0.0
 
# 使用类属性和方法
def test_class_attributes():
    print("=== 类属性与方法测试 ===")
    
    # 访问类属性
    print(f"学校: {Student.school_name}")
    print(f"总学生: {Student.total_students}")
    
    # 创建实例
    student1 = Student("Alice", 120, 360)
    student2 = Student("Bob", 90, 315)
    
    print(f"总学生: {Student.total_students}")
    print(f"学生1 ID: {student1.student_id}")
    print(f"学生2 ID: {student2.student_id}")
    
    # 调用类方法
    print(Student.get_school_info())
    print(Student.set_gpa_scale(5.0))
    
    # 使用替代构造函数
    student3 = Student.create_from_string("Charlie, 100, 380")
    print(f"学生3: {student3.name}, GPA: {student3.gpa():.2f}")
    
    # 调用静态方法
    gpa = Student.calculate_gpa(100, 380)
    print(f"静态方法计算GPA: {gpa:.2f}")
 
# test_class_attributes()

Summary 总结

Key Concepts 关键概念

  1. Object-Oriented Programming面向对象编程

    • Classes as blueprints for objects 类作为对象的蓝图
    • Objects encapsulate data and behavior 对象封装数据和行为
    • Inheritance, polymorphism, encapsulation 继承、多态、封装
  2. Class Definition类定义

    • __init__ constructor method __init__构造方法
    • Instance variables and methods 实例变量和方法
    • Self parameter reference self参数引用
  3. Encapsulation封装

    • Hide implementation details 隐藏实现细节
    • Provide clean interfaces 提供清晰接口
    • Improve maintainability 提高可维护性
  4. Class vs Instance类与实例

    • Class attributes shared by all instances 类属性被所有实例共享
    • Instance attributes unique to each object 实例属性每个对象独有
    • Class methods and static methods 类方法和静态方法
  5. GUI ProgrammingGUI编程

    • Custom widget creation 自定义小部件创建
    • Event-driven programming 事件驱动编程
    • Animation techniques 动画技术

Design Principles 设计原则

  • Process-Oriented Design面向过程设计: Focus on steps and procedures 关注步骤和过程
  • Object-Oriented Design面向对象设计: Focus on objects and their interactions 关注对象及其交互
  • Choose appropriate approach based on problem complexity 根据问题复杂度选择合适方法
  • Use encapsulation to create modular code 使用封装创建模块化代码

通过定义类,可以创建更结构化、可维护和可重用的代码,构建复杂的软件系统!

下一章