books

1.谁动了我的奶酪

2.傲慢与偏见

3.简爱

4.理智与情感

import user import book import csv import datetime from datetime import timedelta import typing class Loan: def __init__(self, user, book, borrow_str, due_str, return_str): self.user = user self.book = book self.borrow_date = parse_date(borrow_str) self.due_date = parse_date(due_str) self.return_date = parse_date(return_str) def calculate_fine(self): TODAY = datetime.datetime.strptime("15/09/2025", "%d/%m/%Y").date() if self.book.book_type == "online": return 0.0 if TODAY <= self.due_date: return 0.0 grace = {"Student": 0, "Staff": 2, "Others": 0}[self.user.role] effective_due = self.due_date + timedelta(days = grace) overdue_days = max(0, (TODAY - effective_due).days) rate = 0.50 if self.user.role in ['Student', 'Staff'] else 1.00 return overdue_days * rate def parse_date(date_str: str) -> typing.Optional[datetime.date]: if not date_str or date_str.strip() == "None": return None day, month, year = map(int, date_str.split('/')) return datetime.date(year, month, day) def load_users(file_path: str) -> dict: users = {} with open(file_path, newline='') as csvfile: reader = csv.reader(csvfile) next(reader) for row in reader: user_id, password, name, role, department = row role = row[3].capitalize() if user_id.startswith('s'): u = user.Student(user_id, password, name, role, None) elif user_id.startswith('e'): u = user.Staff(user_id, password, name, role, department) else: role = "Others" u = user.Other(user_id, password, name, role, None) users[user_id] = u return users def load_books(file_path: str) -> dict: books = {} with open(file_path, newline='') as csvfile: reader = csv.reader(csvfile) next(reader) for row in reader: book_id, book_type, copies, title, author, year, keywords = row b = book.Book(book_id, book_type, int(copies), title, author, int(year), keywords) books[book_id] = b b.loans = [] return books def load_loans(file_path: str, users: dict, books: dict): loans = [] with open(file_path, newline='') as csvfile: reader = csv.reader(csvfile) next(reader) for row in reader: user_id, book_id, borrow_date, due_date, return_date = row if user_id in users and book_id in books: loan = Loan(users[user_id], books[book_id], borrow_date, due_date, return_date) users[user_id].add_loan(loan) books[book_id].loans.append(loan) loans.append(loan) return loans def borrow_return_console(user, books, users): TODAY = datetime.datetime.strptime("15/09/2025", "%d/%m/%Y").date() while True: cmd = input("> ").strip() if cmd == "quit": return part = cmd.split(' ', 1) if len(part) < 2: continue action, query = part[0].lower(), part[1] if action == "borrow": matched_books = [] for b in books.values(): if query.lower() in b.title.lower() or query == b.book_id: matched_books.append(b) matched_books.sort(key=lambda x: x.book_id) if not matched_books: print(f"No books match '{query}'.") continue physical_match = [b for b in matched_books if b.book_type == "physical"] online_match = [b for b in matched_books if b.book_type == "online"] available_str = sum(b.available_copies() for b in physical_match) print(f"Found {len(matched_books)} book(s).") all_matches = physical_match + online_match all_matches.sort(key=lambda x: x.book_id) for b in all_matches: copies = f"Available copies: {available_str}/{b.total_copies}" if b.book_type == "physical" else "Available copies: 0/0" print(f"- {b.book_id} ({b.book_type}) '{b.title}' by {b.authors} ({b.year}). {copies}.") valid_ids = [b.book_id for b in all_matches] while True: borrowid = input(f"Confirm the Book ID you'd like to borrow: ").strip() if borrowid == "quit": break if borrowid not in valid_ids: continue book = books[borrowid] unpaid_fine = sum(loan.calculate_fine() for loan in user.get_active_loans()) if unpaid_fine > 0: print("Borrowing unavailable: unpaid fines. Review your loan details for more info.") break current_physical = sum(1 for loan in user.get_active_loans() if loan.book.book_type == "physical") if current_physical >= user.get_loan_policy()["quota"]: print("Borrowing unavailable: quota reached. Review your loan details for more info.") break if book.book_type == "physical" and book.available_copies() <= 0: print("No copies available.") break due_days = user.get_loan_policy()["days"] due_date = TODAY + timedelta(days = due_days) borrow_str = TODAY.strftime("%d/%m/%Y") due_str = due_date.strftime("%d/%m/%Y") return_str = "None" loan = Loan(user, book, borrow_str, due_str, return_str) user.loans.append(loan) book.loans.append(loan) print(f"You have borrowed '{book.title}' by {book.authors} ({book.year}). Due: {due_date.strftime('%d/%m/%Y')}.") break elif action == "return": borrowid = query.strip() if borrowid not in books: print("No loan record for {}.".format(borrowid)) continue book = books[borrowid] active_loans = [loan for loan in user.get_active_loans() if loan.book.book_id == borrowid] if not active_loans: print(f"No loan record for {borrowid}.") continue loan_to_return = min(active_loans, key=lambda x: x.due_date) user.loans.remove(loan_to_return) return_date = TODAY fine_amount = 0.0 overdue_days = 0 if book.book_type == "physical" and return_date > loan_to_return.due_date: grace = {"Student": 0, "Staff": 2, "Others": 0}[user.role] effective_due = loan_to_return.due_date + timedelta(days = grace) overdue_days = max(0, (TODAY - effective_due).days) rate = 0.50 if user.role in ['Student', 'Staff'] else 1.00 fine_amount = overdue_days * rate status = f"Returned '{book.title}' by {book.authors} ({book.year})." if overdue_days > 0: status += f" Overdue by {overdue_days} day(s). Fine: $ {fine_amount:.2f}" print(status) elif action == "renew": borrowid = query.strip() due_days = user.get_loan_policy()["days"] due_date = TODAY + timedelta(days = due_days) for loan in user.get_active_loans(): if int(loan.calculate_fine()) > 0: print("Renewal denied: You have unpaid fines") continue if TODAY > due_date: print("Renewal denied: This book is already overdue") book = books[borrowid] active_loans = [loan for loan in user.get_active_loans() if loan.book.book_id == borrowid] loan_to_renew = min(active_loans, key=lambda x: x.due_date) if loan_to_renew.renewed: print("Renewal unavailable: Each book can only be renewed once.") continue loan_to_renew.due_date = loan_to_renew.due_date + timedelta(days=5) loan_to_renew.renewed = True print(f"Renew '{book.title}' by {book.authors} (book.year) successfully. New due date: {loan_to_renew.due_date.strftime('%d/%m/%Y')}") else: continue def search_by_keywords(books): keywords = input("Enter search keywords (separated by comma): ").strip().lower().split(',') if not keywords or keywords[0] == '': print("Found 0 book(s).") return result = [] for b in book.values(): matched = sum(1 for keyword in keywords if keywords in book.title.lower() or keywords in ' '.join(book.keywords).lower()) if matched > 0: result.append(book, matched) result.sort(key=lambda x: (-x[1], -x[0].year, x[0].book_id)) print(f"Found {len(result)} book(s).") for i, (book, _) in enumerate(result): print(f"{i+1}. {book.book_id} '{book.title}' by {book.authors} ({book.year}).") def manage_library(books, existing_keywords): while True: cmd = input("> ").strip() if cmd == "quit": break elif cmd == "report": total_user = len(users) students = sum(1 for u in users.values() if u.user_id.startswith('s')) staffs = sum(1 for u in users.values() if u.user_id.startswith('e')) others = sum(1 for u in users.values() if u.user_id.startswith('o')) total_books = len(books) physical_books = sum(1 for b in books.values() if b.book_type == "physical") available_physical = sum(1 for b in books.values() if b.book_type == "physical" and b.is_available()) online_books = sum(1 for b in books.values() if b.book_type == "online") print("Library report") print(f"- {total_user} users, including {students} student(s), {staffs} staff, and {others} others.") print(f"- {total_books} books, including {physical_books} physical book(s) ({available_physical} currently available) and {online_books} online book(s).") elif cmd.startswith("add "): book_type = cmd.split(" ")[1] if book_type not in ["physical", "online"]: print("Invalid cmd.") continue title = input("Title: ") authors = input("Authors: ") year = input("Year: ") if book_type == "physical": copies = input("Copies: ") else: copies = 0 all_keywords = set() for b in books.value(): all_keywords.update(books.keywords) detected_keywords = [word for word in title.lower().split() if word in all_keywords] detected_keywords.sort() keywords_str = ":".join(detected_keywords) if book_type == "physical": physical_ids = [int(book.book_id[1:]) for book in books.values() if book.book_id.startswith('P')] new_id = f"P{str(max(physical_ids) + 1)}" else: online_ids = [int(book.book_id[1:]) for book in books.values() if book.book_id.startswith('E')] new_id = f"E{str(max(online_ids) + 1)}" new_book = book(new_id, book_type, copies, title, authors, year, keywords_str) book[new_id] = new_book print(f"Detected keywords: {keywords_str}") print(f"Adding {new_id} '{title}' by {authors} ({year}).") def main(user_file: str, book_file:str, loan_file:str) -> None: """ This is the entry of your program. Please DO NOT modify this function signature, i.e. function name, parameters Parameteres: - user_file (str): path the `users.csv` which stores user information - book_file (str): path the `books.csv` which stores book information - loan_file (str): path the `loans.csv` which stores loan information """ # Your implemetation goes here users = load_users(user_file) books = load_books(book_file) load_loans(loan_file, users, books) while True: print("Welcome to Library") user_input = input("Login as: ").strip() if user_input == "quit": print("Goodbye!") return else: password = input("Password: ").strip() if user_input not in users or users[user_input].password != password: print("Invalid credentials. 2 attempt(s) remaining.") user_input = input("Login as: ").strip() password = input("Password: ").strip() if user_input not in users or users[user_input].password != password: print("Invalid credentials. 1 attempt(s) remaining.") user_input = input("Login as: ").strip() password = input("Password: ").strip() if user_input not in users or users[user_input].password != password: print("Sorry you're out of attempts. Please contact your librarian for assistance.") continue current_user = users[user_input] print(f"Logged in as {current_user.name} ({current_user.role})") while True: menu = [ "My Library Account", "0. Quit", "1. Log out", "2. View account policies", "3. View my loans", "4. Borrow and Return", "5. Search by Keywords" ] if current_user.is_library_staff(): menu.append("6. Manage Library") print("=" * 34) for item in menu: print(item) print("=" * 34) while True: try: choice = input("Enter your choice: ").strip() if not choice.isdigit() or choice not in ['0', '1', '2', '3', '4', '5', '6']: continue choice = int(choice) break except: continue if choice == 0: print("Goodbye!") return elif choice == 1: break elif choice == 2: policy = current_user.get_loan_policy() active_loans = current_user.get_active_loans() num_loans = len(active_loans) phys, online = current_user.get_physical_online_count() total_fine = sum(loan.calculate_fine() for loan in active_loans) print(f"{current_user.role} {current_user.name}. Policies: maximum of {policy['days']} days, {policy['quota']} items. Current loans: {num_loans} ({phys} physical / {online} online). Fines: $ {total_fine:.2f}") elif choice == 3: active_loans = sorted(current_user.get_active_loans(), key=lambda x: x.due_date) count = len(active_loans) print(f"You are currently have {count} loan(s).") for i, loan in enumerate(active_loans, 1): print(f"{i}. {loan.book.book_id} '{loan.book.title}' by {loan.book.authors} ({loan.book.year}). Due date: {loan.due_date.strftime('%d/%m/%Y')}.") elif choice == 4: borrow_return_console(current_user, books, users) elif choice == 5: total_user = len(users) students = sum(1 for u in users.values() if u.user_id.startswith('s')) staffs = sum(1 for u in users.values() if u.user_id.startswith('e')) others = sum(1 for u in users.values() if u.user_id.startswith('o')) total_books = len(books) physical_books = sum(1 for b in books.values() if b.book_type == "physical") available_physical = sum(1 for b in books.values() if b.book_type == "physical" and b.is_available()) online_books = sum(1 for b in books.values() if b.book_type == "online") print("Library report") print(f"- {total_user} users, including {students} student(s), {staffs} staff, and {others} others.") print(f"- {total_books} books, including {physical_books} physical book(s) ({available_physical} currently available) and {online_books} online book(s).") else: continue if __name__ == "__main__": main('data/users.csv', 'data/books.csv', 'data/loans.csv')
最新发布
10-14
你的代码整体结构清晰,功能完整,但存在多个 **关键错误和潜在 bug**,导致程序在运行时会崩溃或行为异常。我们来逐个分析并修复问题。 --- ## 🔴 问题 1:`KeyError: 'P0101'` 的根本原因 你遇到的 `KeyError: 'P0101'` 是因为你在 `borrow_return_console` 中尝试访问 `books[borrowid]`,而该 ID 不在 `books` 字典中。 但是从你的代码来看,这个问题很可能是因为: ### ❌ 错误地使用了 `valid_ids` 列表 ```python valid_ids = [b.book_id for b in all_matches] ``` 这里 `all_matches` 包含的是搜索结果中的书(已过滤),但后续你直接用用户输入的 `borrowid` 去查全局 `books` 字典: ```python book = books[borrowid] # ⚠️ 这里可能 KeyError! ``` 虽然你检查了 `borrowid not in valid_ids`,但由于 `valid_ids` 来自局部变量 `all_matches`,如果用户输入了一个不在搜索结果里的合法书籍 ID(比如拼错、或者跳过搜索直接输错 ID),就会出错。 更严重的是:即使 `borrowid` 在 `valid_ids` 中,也不能保证它一定在 `books` 字典中 —— 实际上应该是一致的,但如果加载失败就不是。 ✅ **解决方案:始终确保 `books` 加载正确,并对所有访问做安全检查** --- ## ✅ 修复建议与完整修正版代码(重点部分) ### ✅ 修复点 1:防止 `KeyError` 安全访问书籍 修改 `borrow` 分支中获取书籍的部分: ```python # 原始代码有风险 book = books[borrowid] # 改为: if borrowid not in books: print(f"❌ Book ID '{borrowid}' does not exist.") continue book = books[borrowid] ``` 同时,在 `return` 和 `renew` 操作中也应如此处理。 --- ### ✅ 修复点 2:`search_by_keywords` 函数语法错误严重 当前函数中有多个致命错误: ```python def search_by_keywords(books): ... for b in book.values(): # ❌ book 未定义,应为 books matched = sum(...) # ❌ keywords in keywords? 逻辑错误 result.append(book, matched) # ❌ append 接受一个参数 ``` #### ✅ 正确实现如下: ```python def search_by_keywords(books): keyword_input = input("Enter search keywords (separated by comma): ").strip().lower() if not keyword_input: print("Found 0 book(s).") return keywords = [k.strip() for k in keyword_input.split(',') if k.strip()] result = [] for b in books.values(): title_match = any(k in b.title.lower() for k in keywords) keyword_match = any(k in kw.lower() for k in keywords for kw in b.keywords) if title_match or keyword_match: match_count = sum( 1 for k in keywords if k in b.title.lower() or any(k in kw.lower() for kw in b.keywords) ) result.append((b, match_count)) # Sort by: match count ↓, year ↓, book_id ↑ result.sort(key=lambda x: (-x[1], -x[0].year, x[0].book_id)) print(f"Found {len(result)} book(s).") for i, (book, matches) in enumerate(result, 1): copies_info = f"Available copies: {book.available_copies()}/{book.total_copies}" \ if book.book_type == "physical" else "Available copies: N/A" print(f"{i}. {book.book_id} '{book.title}' by {book.authors} ({book.year}). {copies_info}") ``` > ⚠️ 注意:假设 `Book` 类中 `keywords` 是列表类型,如 `["AI", "Machine Learning"]` --- ### ✅ 修复点 3:`manage_library` 函数中的严重语法错误 #### ❌ 原始错误代码: ```python for b in books.value(): # ❌ 应为 .values() all_keywords.update(books.keywords) # ❌ books 是 dict,没有 .keywords ... new_book = book(new_id, ...) # ❌ 小写 book 是模块名,不能当构造器 book[new_id] = new_book # ❌ 同样,book 是模块 ``` #### ✅ 修复后版本: ```python def manage_library(users, books): while True: cmd = input("> ").strip() if cmd == "quit": break elif cmd == "report": total_user = len(users) students = sum(1 for u in users.values() if u.user_id.startswith('s')) staffs = sum(1 for u in users.values() if u.user_id.startswith('e')) others = sum(1 for u in users.values() if u.user_id.lower().startswith('o')) total_books = len(books) physical_books = sum(1 for b in books.values() if b.book_type == "physical") available_physical = sum(b.available_copies() for b in books.values() if b.book_type == "physical") online_books = sum(1 for b in books.values() if b.book_type == "online") print("Library report") print(f"- {total_user} users, including {students} student(s), {staffs} staff, and {others} others.") print(f"- {total_books} books, including {physical_books} physical book(s) " f"({available_physical} currently available) and {online_books} online book(s).") elif cmd.startswith("add "): parts = cmd.split(" ", 2) if len(parts) < 2: print("Invalid command format. Use: add <physical|online>") continue book_type = parts[1] if book_type not in ["physical", "online"]: print("Invalid book type. Use 'physical' or 'online'.") continue title = input("Title: ").strip() authors = input("Authors: ").strip() try: year = int(input("Year: ").strip()) except ValueError: print("Invalid year.") continue if book_type == "physical": try: copies = int(input("Copies: ").strip()) except ValueError: print("Invalid number of copies.") continue else: copies = 0 # Detect existing keywords from all books all_keywords_set = set() for b in books.values(): all_keywords_set.update(kw.lower() for kw in b.keywords) detected_keywords = list(set(word.lower() for word in title.split()) & all_keywords_set) detected_keywords.sort() keywords_str = ":".join(detected_keywords) print(f"Detected keywords: {keywords_str}") # Generate new ID prefix = 'P' if book_type == "physical" else 'E' existing_ids = [ int(b.book_id[1:]) for b in books.values() if b.book_id.startswith(prefix) and b.book_id[1:].isdigit() ] new_id_num = max(existing_ids) + 1 if existing_ids else 1 new_id = f"{prefix}{new_id_num:04d}" # e.g., P0101 # Create new book using the Book class new_book = book.Book(new_id, book_type, copies, title, authors, year, detected_keywords) books[new_id] = new_book print(f"✅ Added {new_id} '{title}' by {authors} ({year}). Keywords: {keywords_str}") else: print("Unknown command. Type 'report' or 'add physical/online'") ``` > 📝 注意:调用的是 `book.Book(...)`,不是小写的 `book(...)` --- ### ✅ 修复点 4:`renew` 功能逻辑错误 原代码判断 fine 是否 > 0 写法有问题: ```python for loan in user.get_active_loans(): if int(loan.calculate_fine()) > 0: # ❌ 即使一本有 fine 就拒绝全部? print("Renewal denied...") continue # ❌ continue 只跳出循环,不中断外层 ``` 这会导致打印多次提示信息,且无法阻止续借。 #### ✅ 正确做法: ```python elif action == "renew": borrowid = query.strip() if borrowid not in books: print(f"No such book with ID '{borrowid}'.") continue book = books[borrowid] active_loans = [loan for loan in user.get_active_loans() if loan.book.book_id == borrowid] if not active_loans: print(f"You have no active loan for '{borrowid}'.") continue loan_to_renew = min(active_loans, key=lambda x: x.due_date) # Check unpaid fines if loan_to_renew.calculate_fine() > 0: print("Renewal denied: You have unpaid fines.") continue # Check already overdue if TODAY > loan_to_renew.due_date: grace = {"Student": 0, "Staff": 2, "Others": 0}[user.role] effective_due = loan_to_renew.due_date + timedelta(days=grace) if TODAY > effective_due: print("Renewal denied: This book is already overdue beyond grace period.") continue # Check renewal limit if hasattr(loan_to_renew, 'renewed') and loan_to_renew.renewed: print("Renewal unavailable: Each book can only be renewed once.") continue # Perform renewal loan_to_renew.due_date += timedelta(days=5) if not hasattr(loan_to_renew, 'renewed'): loan_to_renew.renewed = False # 初始化字段(可选) loan_to_renew.renewed = True print(f"✅ Renewed '{book.title}' successfully. New due date: {loan_to_renew.due_date.strftime('%d/%m/%Y')}") ``` --- ### ✅ 其他小问题汇总 | 问题 | 说明 | |------|------| | `TODAY = ...` 被多次定义 | 提取为常量更好 | | `book.is_available()` 方法未定义 | 需要在 `Book` 类中实现 | | 用户三次登录失败后仍继续主循环 | 应 `continue` 回到欢迎界面 | --- ## ✅ 最佳实践建议 ### ✅ 建议添加的 `Book.is_available()` 方法 ```python # 在 book.py 中 def is_available(self): return self.available_copies() > 0 ``` ### ✅ 统一 `TODAY` 时间常量 ```python TODAY = datetime.datetime.strptime("15/09/2025", "%d/%m/%Y").date() ``` 放在文件顶部作为全局常量。 --- ## ✅ 总结:你现在最需要改的地方 - ✅ 修复 `search_by_keywords` 的语法错误 - ✅ 修复 `manage_library` 中的变量命名错误(`book` vs `books`) - ✅ 所有 `books[borrowid]` 前加 `if borrowid in books` - ✅ `renew` 功能要单独检查目标 loan 的 fine - ✅ 确保 `Loan` 对象上有 `renewed` 属性初始化 ---
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值