百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术资源 > 正文

Python进阶-day21:复习与小项目

off999 2025-05-21 15:46 134 浏览 0 评论

学习目标

  1. 复习内容:巩固OOP、异常处理、文件操作、模块化等知识。
  2. 高级概念设计模式:单例模式(确保账户唯一性)、工厂模式(创建交易对象)。 上下文管理:管理文件操作和数据库连接。 元编程:动态添加方法或属性。 多线程:异步保存数据和统计分析。
  3. 小项目:开发一个功能强大的命令行个人财务管理系统,支持持久化、并发操作和动态扩展。
  4. 实践技能:通过项目综合应用高级Python技术,提升代码设计能力。

时间分配(总计:5-7小时)

  • 1小时:复习本周内容
  • 4-5小时:开发个人财务管理系统项目(融入高级概念)
  • 1小时:项目测试、优化与总结
  • 你已掌握Python基础、OOP、文件操作、模块化编程。
  • 本周学习了OOP、异常处理、文件操作、模块化、数据结构等。
  • 熟悉标准库(如json、datetime、os)。

一、复习内容(1小时)

复习重点

  1. 面向对象编程(OOP): 类、继承、封装、多态 私有属性、属性装饰器(@property)
  2. 异常处理: try-except-else-finally 自定义异常类
  3. 文件操作: 读写文本、JSON、CSV with语句管理资源
  4. 模块与包: 模块导入、包组织 init.py与相对导入
  5. 数据结构: 列表、字典、集合 推导式、高阶函数(map、filter)

复习方法

  1. 快速回顾笔记(20分钟)
  2. 复习笔记或代码,重点关注OOP、异常处理、文件操作。
  3. 写下每个知识点的核心代码示例。例如:python
  4. # OOP 示例:使用属性装饰器 class Account: def __init__(self): self._balance = 0 @property def balance(self): return self._balance @balance.setter def balance(self, value): if value < 0: raise ValueError("Balance cannot be negative") self._balance = value
  5. 练习题(30分钟)
  6. 完成以下练习: 定义一个Transaction类,使用@property管理金额属性,确保金额为正。 编写一个函数,从transactions.json读取交易记录,处理文件不存在的异常。 创建一个模块utils.py,实现一个高阶函数,过滤金额大于某个阈值的交易。 使用try-except处理用户输入,验证日期格式(YYYY-MM-DD)。
  7. 总结问题(10分钟)
  8. 记录复习中的薄弱点,留待项目中实践。

二、小项目:命令行个人财务管理系统(4-5小时)

项目概述

开发一个命令行个人财务管理系统,支持:

  • 记录收入和支出(金额、类别、日期、备注)。
  • 查看账户余额、交易历史。
  • 按类别或日期筛选交易。
  • 统计某类别的总收入/支出。
  • 数据持久化(JSON文件)。
  • 并发保存数据和统计分析。

融入高级概念

  1. 设计模式单例模式:确保全局只有一个Account实例。 工厂模式:通过工厂类创建不同类型的交易(收入、支出)。
  2. 上下文管理: 使用contextlib管理文件操作,确保资源正确关闭。 实现自定义上下文管理器,记录操作日志。
  3. 元编程: 使用metaclass动态为Transaction类添加审计方法。 使用setattr动态添加统计方法。
  4. 多线程: 使用threading异步保存数据到文件。 使用线程池执行统计分析。

项目功能需求

  1. 主菜单: 添加收入/支出 查看余额、交易历史 筛选交易(按类别或日期) 统计分析 退出并保存
  2. 数据存储: 使用JSON文件存储交易和余额。 异步保存数据。
  3. 输入验证: 验证金额、日期、类别。
  4. 统计功能: 异步计算类别统计。
  5. 日志: 使用上下文管理器记录操作日志。

项目结构

personal_finance/
├── main.py           # 主程序入口
├── account.py        # 账户和交易逻辑(单例模式、工厂模式、元编程)
├── storage.py        # 文件操作(上下文管理、多线程)
├── menu.py           # 用户交互界面
├── logger.py         # 日志管理(上下文管理)
└── data.json         # 数据存储文件

项目实现步骤

1. 日志管理(logger.py)

实现上下文管理器,记录操作日志。

python

# logger.py
from contextlib import contextmanager
import logging
from datetime import datetime

# 配置日志
logging.basicConfig(
    filename="finance.log",
    level=logging.INFO,
    format="%(asctime)s - %(levelname)s - %(message)s"
)

@contextmanager
def operation_logger(operation_name):
    """上下文管理器,记录操作开始和结束"""
    logging.info(f"Starting operation: {operation_name}")
    start_time = datetime.now()
    try:
        yield
        logging.info(f"Completed operation: {operation_name}, Duration: {datetime.now() - start_time}")
    except Exception as e:
        logging.error(f"Failed operation: {operation_name}, Error: {str(e)}")
        raise

2. 账户与交易逻辑(account.py)

实现单例模式、工厂模式和元编程。

python

# account.py
from datetime import datetime
import uuid
from abc import ABC, ABCMeta, abstractmethod
from logger import operation_logger  # Import operation_logger

class TransactionMeta(ABCMeta):
    """Custom metaclass for Transaction, inheriting from ABCMeta to resolve metaclass conflict"""
    def __new__(cls, name, bases, attrs):
        # Dynamically add an audit method to the class
        def audit(self):
            return f"Audit: Transaction {self.id} created at {self.created_at}"
        attrs['audit'] = audit
        return super().__new__(cls, name, bases, attrs)

class Transaction(ABC, metaclass=TransactionMeta):
    """Abstract base class for transactions"""
    def __init__(self, amount, category, date=None, note=""):
        if amount <= 0:
            raise ValueError("Amount must be positive")
        self.id = str(uuid.uuid4())[:8]  # Unique transaction ID
        self.amount = amount
        self.category = category
        self.date = date or datetime.now().strftime("%Y-%m-%d")
        self.note = note
        self.created_at = datetime.now()

    @abstractmethod
    def apply(self, balance):
        """Abstract method to apply transaction to balance"""
        pass

class IncomeTransaction(Transaction):
    """Concrete class for income transactions"""
    def apply(self, balance):
        return balance + self.amount

class ExpenseTransaction(Transaction):
    """Concrete class for expense transactions"""
    def apply(self, balance):
        if balance < self.amount:
            raise ValueError("Insufficient balance")
        return balance - self.amount

class TransactionFactory:
    """Factory class to create transaction objects"""
    @staticmethod
    def create_transaction(trans_type, amount, category, date=None, note=""):
        if trans_type == "income":
            return IncomeTransaction(amount, category, date, note)
        elif trans_type == "expense":
            return ExpenseTransaction(amount, category, date, note)
        else:
            raise ValueError("Invalid transaction type")

class Account:
    """Singleton class for managing the account"""
    _instance = None

    def __new__(cls):
        if cls._instance is None:
            cls._instance = super(Account, cls).__new__(cls)
            cls._instance.balance = 0
            cls._instance.transactions = []
        return cls._instance

    def add_transaction(self, trans_type, amount, category, date=None, note=""):
        """Add a transaction to the account"""
        with operation_logger(f"Add {trans_type} transaction"):
            transaction = TransactionFactory.create_transaction(trans_type, amount, category, date, note)
            self.balance = transaction.apply(self.balance)
            self.transactions.append(transaction)
            print(f"Transaction audit: {transaction.audit()}")  # Call dynamically added audit method
            return transaction

    def get_balance(self):
        """Return the current balance"""
        return self.balance

    def get_transactions(self, category=None, start_date=None, end_date=None):
        """Filter transactions by category, start date, or end date"""
        result = self.transactions
        if category:
            result = [t for t in result if t.category == category]
        if start_date:
            result = [t for t in result if t.date >= start_date]
        if end_date:
            result = [t for t in result if t.date <= end_date]
        return result

    def get_category_summary(self, category):
        """Calculate total income and expense for a category"""
        total_income = sum(t.amount for t in self.transactions
                          if t.category == category and isinstance(t, IncomeTransaction))
        total_expense = sum(t.amount for t in self.transactions
                           if t.category == category and isinstance(t, ExpenseTransaction))
        return {"income": total_income, "expense": total_expense}

3. 文件操作(storage.py)

实现上下文管理和多线程保存。

python

# storage.py
import json
import os
from contextlib import contextmanager
from threading import Thread
from logger import operation_logger

class Storage:
    def __init__(self, filename="data.json"):
        self.filename = filename

    @contextmanager
    def file_handler(self, mode):
        """上下文管理器:管理文件操作"""
        file = open(self.filename, mode)
        try:
            yield file
        finally:
            file.close()

    def save_async(self, account):
        """异步保存数据"""
        def save_task():
            with operation_logger("Save data"):
                data = {
                    "balance": account.balance,
                    "transactions": [
                        {
                            "id": t.id,
                            "amount": t.amount,
                            "category": t.category,
                            "trans_type": "income" if isinstance(t, IncomeTransaction) else "expense",
                            "date": t.date,
                            "note": t.note
                        } for t in account.transactions
                    ]
                }
                with self.file_handler("w") as f:
                    json.dump(data, f, indent=4)
        thread = Thread(target=save_task)
        thread.start()
        return thread

    def load(self):
        """加载数据"""
        if not os.path.exists(self.filename):
            return None
        with operation_logger("Load data"):
            with self.file_handler("r") as f:
                return json.load(f)

4. 用户界面(menu.py)

实现交互界面,支持异步操作。

python

# menu.py
from account import Account, TransactionFactory, IncomeTransaction, ExpenseTransaction  # Added imports
from storage import Storage
from logger import operation_logger
from datetime import datetime
from concurrent.futures import ThreadPoolExecutor

def validate_date(date_str):
    """验证日期格式"""
    try:
        datetime.strptime(date_str, "%Y-%m-%d")
        return True
    except ValueError:
        return False

def async_summary(account, category):
    """异步计算类别统计"""
    return account.get_category_summary(category)

def main_menu():
    account = Account()
    storage = Storage()

    # 加载数据
    with operation_logger("Initialize account"):
        data = storage.load()
        if data:
            account.balance = data["balance"]
            for t in data["transactions"]:
                account.add_transaction(
                    t["trans_type"], t["amount"], t["category"], t["date"], t["note"]
                )

    # 动态添加统计方法(元编程)
    def monthly_summary(self, year, month):
        """动态添加:按月统计"""
        total_income = sum(t.amount for t in self.transactions
                           if t.date.startswith(f"{year}-{month:02d}") and isinstance(t, IncomeTransaction))
        total_expense = sum(t.amount for t in self.transactions
                            if t.date.startswith(f"{year}-{month:02d}") and isinstance(t, ExpenseTransaction))
        return {"income": total_income, "expense": total_expense}

    setattr(Account, "monthly_summary", monthly_summary)

    with ThreadPoolExecutor(max_workers=2) as executor:
        while True:
            print("\\n=== Personal Finance Manager ===")
            print("1. Add Income")
            print("2. Add Expense")
            print("3. View Balance")
            print("4. View Transactions")
            print("5. Filter Transactions")
            print("6. Category Summary")
            print("7. Monthly Summary")
            print("8. Exit")
            choice = input("Enter choice (1-8): ")

            try:
                if choice in ["1", "2"]:
                    with operation_logger(f"Add {'income' if choice == '1' else 'expense'}"):
                        amount = float(input("Enter amount: "))
                        category = input("Enter category (e.g., Salary, Food): ")
                        date = input("Enter date (YYYY-MM-DD, press Enter for today): ")
                        if date and not validate_date(date):
                            print("Invalid date format!")
                            continue
                        note = input("Enter note (optional): ")
                        trans_type = "income" if choice == "1" else "expense"
                        account.add_transaction(trans_type, amount, category, date or None, note)
                        print(f"{trans_type.capitalize()} added successfully!")

                elif choice == "3":
                    print(f"Current Balance: ${account.get_balance():.2f}")

                elif choice == "4":
                    transactions = account.get_transactions()
                    if not transactions:
                        print("No transactions found.")
                    for t in transactions:
                        print(f"ID: {t.id}, Type: {'Income' if isinstance(t, IncomeTransaction) else 'Expense'}, "
                              f"Amount: ${t.amount:.2f}, Category: {t.category}, Date: {t.date}, Note: {t.note}")

                elif choice == "5":
                    category = input("Enter category to filter (press Enter to skip): ")
                    start_date = input("Enter start date (YYYY-MM-DD, press Enter to skip): ")
                    if start_date and not validate_date(start_date):
                        print("Invalid start date!")
                        continue
                    end_date = input("Enter end date (YYYY-MM-DD, press Enter to skip): ")
                    if end_date and not validate_date(end_date):
                        print("Invalid end date!")
                        continue
                    transactions = account.get_transactions(category or None, start_date or None, end_date or None)
                    if not transactions:
                        print("No transactions found.")
                    for t in transactions:
                        print(f"ID: {t.id}, Type: {'Income' if isinstance(t, IncomeTransaction) else 'Expense'}, "
                              f"Amount: ${t.amount:.2f}, Category: {t.category}, Date: {t.date}, Note: {t.note}")

                elif choice == "6":
                    category = input("Enter category: ")
                    with operation_logger(f"Category summary for {category}"):
                        future = executor.submit(async_summary, account, category)
                        summary = future.result()
                        print(f"Category: {category}")
                        print(f"Total Income: ${summary['income']:.2f}")
                        print(f"Total Expense: ${summary['expense']:.2f}")

                elif choice == "7":
                    year = int(input("Enter year (e.g., 2025): "))
                    month = int(input("Enter month (1-12): "))
                    with operation_logger(f"Monthly summary for {year}-{month:02d}"):
                        summary = account.monthly_summary(year, month)
                        print(f"Monthly Summary ({year}-{month:02d})")
                        print(f"Total Income: ${summary['income']:.2f}")
                        print(f"Total Expense: ${summary['expense']:.2f}")

                elif choice == "8":
                    with operation_logger("Exit and save"):
                        storage.save_async(account).join()  # 等待保存完成
                        print("Data saved. Goodbye!")
                        break

                else:
                    print("Invalid choice!")

            except ValueError as e:
                print(f"Error: {e}")
            except Exception as e:
                print(f"An unexpected error occurred: {e}")

5. 主程序(main.py)

启动程序。

python

# main.py
from menu import main_menu

if __name__ == "__main__":
    main_menu()

6. 测试与调试(1小时)

  • 测试用例: 添加收入(1000,Salary,2025-04-22,"Monthly salary")。 添加支出(200,Food,2025-04-22,"Dinner")。 查看余额(应为800)。 查看所有交易。 筛选Food类别的交易。 查看Salary类别的统计(异步)。 查看2025年4月月度统计。 退出并检查data.json和finance.log。
  • 调试: 检查单例模式(多次实例化Account应返回同一对象)。 验证异步保存(data.json是否正确更新)。 测试元编程(audit方法和monthly_summary是否正常工作)。 确保上下文管理器记录所有操作日志。
  • 优化: 使用argparse支持命令行参数。 添加删除交易功能。 使用tabulate美化输出。

三、项目测试、优化与总结(1小时)

测试

  1. 运行程序,逐一测试所有功能。
  2. 故意输入错误数据(负金额、无效日期),验证异常处理。
  3. 检查finance.log,确保所有操作被记录。
  4. 重启程序,确认数据正确加载。

优化建议

  1. 添加数据库支持(sqlite3)。
  2. 使用asyncio替代threading,提升异步性能。
  3. 实现更多设计模式(如观察者模式,用于交易通知)。
  4. 添加单元测试(使用unittest或pytest)。

总结

  1. 知识点回顾设计模式:单例模式(Account)、工厂模式(TransactionFactory)。 上下文管理:文件操作(Storage.file_handler)、日志(operation_logger)。 元编程:动态添加audit和monthly_summary方法。 多线程:异步保存(save_async)、异步统计(async_summary)。 OOP:继承、抽象类、封装。 异常处理:输入验证、文件操作错误。 模块化:逻辑分离到多个文件。
  2. 问题与解决方案: 记录调试中遇到的问题(如线程同步、元类调试)。 总结解决方案(如使用Thread.join()确保保存完成)。
  3. 扩展方向: 添加GUI(tkinter)。 集成pandas进行数据分析。 实现REST API(使用FastAPI)。

学习成果

  • 高级技能:掌握了单例模式、工厂模式、上下文管理、元编程、多线程。
  • 项目经验:开发了一个模块化、可扩展的命令行应用。
  • 代码质量:通过设计模式和上下文管理提升了代码健壮性和可维护性。

下一步建议

  • 深入学习设计模式(如观察者模式、策略模式)。
  • 探索asyncio和并发编程。
  • 学习元编程高级应用(如自定义描述器)。
  • 开发更复杂的项目(如Web应用、数据分析工具)。

相关推荐

安全教育登录入口平台(安全教育登录入口平台官网)

122交通安全教育怎么登录:122交通网的注册方法是首先登录网址http://www.122.cn/,接着打开网页后,点击右上角的“个人登录”;其次进入邮箱注册,然后进入到注册页面,输入相关信息即可完...

大鱼吃小鱼经典版(大鱼吃小鱼经典版(经典版)官方版)

大鱼吃小鱼小鱼吃虾是于谦跟郭麒麟的《我的棒儿呢?》郭德纲说于思洋郭麒麟作诗的相声,最后郭麒麟做了一首,师傅躺在师母身上大鱼吃小鱼小鱼吃虾虾吃水水落石出师傅压师娘师娘压床床压地地动山摇。...

谷歌地球下载高清卫星地图(谷歌地球地图下载器)
  • 谷歌地球下载高清卫星地图(谷歌地球地图下载器)
  • 谷歌地球下载高清卫星地图(谷歌地球地图下载器)
  • 谷歌地球下载高清卫星地图(谷歌地球地图下载器)
  • 谷歌地球下载高清卫星地图(谷歌地球地图下载器)
哪个软件可以免费pdf转ppt(免费的pdf转ppt软件哪个好)
哪个软件可以免费pdf转ppt(免费的pdf转ppt软件哪个好)

要想将ppt免费转换为pdf的话,我们建议大家可以下一个那个wps,如果你是会员的话,可以注册为会员,这样的话,在wps里面的话,就可以免费将ppt呢转换为pdfpdf之后呢,我们就可以直接使用,不需要去直接不需要去另外保存,为什么格式转...

2026-02-04 09:03 off999

电信宽带测速官网入口(电信宽带测速官网入口app)

这个网站看看http://www.swok.cn/pcindex.jsp1.登录中国电信网上营业厅,宽带光纤,贴心服务,宽带测速2.下载第三方软件,如360等。进行在线测速进行宽带测速时,尽...

植物大战僵尸95版手机下载(植物大战僵尸95 版下载)

1可以在应用商店或者游戏平台上下载植物大战僵尸95版手机游戏。2下载教程:打开应用商店或者游戏平台,搜索“植物大战僵尸95版”,找到游戏后点击下载按钮,等待下载完成即可安装并开始游戏。3注意:确...

免费下载ppt成品的网站(ppt成品免费下载的网站有哪些)

1、Chuangkit(chuangkit.com)直达地址:chuangkit.com2、Woodo幻灯片(woodo.cn)直达链接:woodo.cn3、OfficePlus(officeplu...

2025世界杯赛程表(2025世界杯在哪个国家)

2022年卡塔尔世界杯赛程公布,全部比赛在卡塔尔境内8座球场举行,2022年,决赛阶段球队全部确定。揭幕战于当地时间11月20日19时进行,由东道主卡塔尔对阵厄瓜多尔,决赛于当地时间12月18日...

下载搜狐视频电视剧(搜狐电视剧下载安装)

搜狐视频APP下载好的视频想要导出到手机相册里方法如下1、打开手机搜狐视频软件,进入搜狐视频后我们点击右上角的“查找”,找到自已喜欢的视频。2、在“浏览器页面搜索”窗口中,输入要下载的视频的名称,然后...

pubg免费下载入口(pubg下载入口官方正版)
  • pubg免费下载入口(pubg下载入口官方正版)
  • pubg免费下载入口(pubg下载入口官方正版)
  • pubg免费下载入口(pubg下载入口官方正版)
  • pubg免费下载入口(pubg下载入口官方正版)
永久免费听歌网站(丫丫音乐网)

可以到《我爱音乐网》《好听音乐网》《一听音乐网》《YYMP3音乐网》还可以到《九天音乐网》永久免费听歌软件有酷狗音乐和天猫精灵,以前要跳舞经常要下载舞曲,我从QQ上找不到舞曲下载就从酷狗音乐上找,大多...

音乐格式转换mp3软件(音乐格式转换器免费版)

有两种方法:方法一在手机上操作:1、进入手机中的文件管理。2、在其中选择“音乐”,将显示出手机中的全部音乐。3、点击“全选”,选中所有音乐文件。4、点击屏幕右下方的省略号图标,在弹出菜单中选择“...

电子书txt下载(免费的最全的小说阅读器)

1.Z-library里面收录了近千万本电子书籍,需求量大。2.苦瓜书盘没有广告,不需要账号注册,使用起来非常简单,直接搜索预览下载即可。3.鸠摩搜书整体风格简洁清晰,书籍资源丰富。4.亚马逊图书书籍...

最好免费观看高清电影(播放免费的最好看的电影)

在目前的网上选择中,IMDb(互联网电影数据库)被认为是最全的电影网站之一。这个网站提供了各种类型的电影和电视节目的海量信息,包括剧情介绍、演员表、评价、评论等。其还提供了有关电影制作背后的详细信息,...

孤单枪手2简体中文版(孤单枪手2简体中文版官方下载)

要将《孤胆枪手2》游戏的征兵秘籍切换为中文,您可以按照以下步骤进行操作:首先,打开游戏设置选项,通常可以在游戏主菜单或游戏内部找到。然后,寻找语言选项或界面选项,点击进入。在语言选项中,选择中文作为游...

取消回复欢迎 发表评论: