1. 为什么说“万物皆对象”?从生活到代码的类与对象初体验
刚学Python那会儿,听到“类”和“对象”这两个词,总觉得特别抽象,像是教科书里硬邦邦的概念。直到有一次,我想写个小程序来管理我收藏的几百本书,情况才彻底改变。我发现,如果不用“类”,我的代码会变得一团糟:每本书的信息都用独立的变量存,找起来麻烦,想统一做个操作(比如批量改个价格)更是得写一堆重复的代码。那一刻我突然明白了,类(Class)其实就是我们给现实世界中的“一类事物”画的蓝图,而对象(Object)就是根据这张蓝图造出来的、活生生的具体东西。
就拿“书”来举例。世界上有无数本书,但每本书都有一些共同的“特征”和“行为”。特征就是属性,比如书名、作者、出版日期、版本;行为就是方法,比如可以被出售、可以被借阅、可以显示简介。我们不需要为每一本具体的《Python编程从入门到实践》都重新发明一套描述体系,只需要定义一次“书”这个类,然后“生产”出一个个具体的书对象就行了。这个过程,就叫实例化。
下面这个最简单的类定义,就是那张“蓝图”:
class Book:
"""一个简单的书籍类"""
pass
class 是定义类的关键字,Book 是我们给这个类起的名字,按照惯例,类名首字母大写。里面的 pass 是个占位符,意思是“这里暂时啥也不干”。虽然这个类现在空空如也,但我们已经可以拿它来“造”对象了:
# 根据Book类,创建两个具体的书对象
my_book = Book()
your_book = Book()
print(type(my_book)) # 输出:<class '__main__.Book'>
print(my_book is your_book) # 输出:False
看,my_book 和 your_book 都是 Book 类的实例,也就是对象。type() 函数告诉我们它的类型是 Book。最关键的是最后一行,它证明了两者虽然“长相”一样(都是Book类造的),但却是内存中两个完全独立、互不干扰的个体,就像用同一个模具压出来的两块饼干。
但光有个空壳子没用,我们的书还得有名字和作者。这就需要用到类里面一个非常特殊的方法:__init__()。你可以把它理解为对象的“出生证明”填写处,或者叫构造方法。当使用 Book() 创建对象时,Python会自动调用这个方法。我们可以在里面定义对象刚诞生时就必须要有的属性。
2. 让对象“活”起来:构造方法与实例属性
让我们把刚才那个“空壳”Book类升级一下,赋予它真正的属性。__init__ 方法的第一参数永远是 self,你可以把它看作对象自己的“代号”或者“身份证”。在方法内部,我们通过 self.属性名 = 参数 的方式,把外部传入的数据“绑定”到这个对象身上,这些绑定的数据就叫实例属性。
class Book:
"""一个带有初始化方法的书籍类"""
def __init__(self, name, author, data, version=1):
self.name = name # 书名
self.author = author # 作者
self.data = data # 出版日期
self.version = version # 版本,默认为1
self.is_sold = False # 销售状态,每个新书对象默认未售出
这次,创建对象时就必须提供必要信息了:
# 创建一本具体的书对象
book1 = Book("Python核心编程", "Wesley Chun", "2022-10-01", 3)
book2 = Book("流畅的Python", "Luciano Ramalho", "2021-05-01")
print(f"书名:{book1.name}, 作者:{book1.author}, 版本:v{book1.version}")
print(f"书名:{book2.name}, 版本:v{book2.version}") # 使用了默认版本号1
注意到区别了吗?book1 和 book2 拥有各自独立的 name、author 等属性。修改 book1.name 绝不会影响 book2.name。这里的 version=1 是默认参数,意味着如果创建时不指定版本,就会自动设为1,这在实际编程中非常常用,能给函数和方法调用带来很大的灵活性。
属性有了,对象还应该能“做事”。比如,书可以卖。这就是我们要给类添加的实例方法。实例方法的第一个参数也必须是 self,这样方法内部才能操作这个对象自己的属性。
class Book:
def __init__(self, name, author, data, version=1):
self.name = name
self.author = author
self.data = data
self.version = version
self.is_sold = False
self.price = 0 # 新增一个价格属性,初始为0
# 定义一个卖书的方法
def sell(self, price):
"""设置价格并标记为已售出"""
if price <= 0:
print("价格必须为正数!")
return
self.price = price
self.is_sold = True
print(f"《{self.name}》已售出,售价:{self.price}元")
# 定义一个介绍自己的方法
def introduce(self):
print(f"您好,这是《{self.name}》,作者是{self.author},当前是第{self.version}版。")
现在,我们的书对象能“行动”了:
my_novel = Book("三体", "刘慈欣", "2008-01-01")
my_novel.introduce() # 输出:您好,这是《三体》,作者是刘慈欣,当前是第1版。
my_novel.sell(68) # 输出:《三体》已售出,售价:68元
print(f"是否已售:{my_novel.is_sold}") # 输出:是否已售:True
方法 sell 不仅接收一个外部参数 price,更重要的是,它通过 self 修改了对象自身的状态(self.price 和 self.is_sold)。这就是面向对象编程的核心魅力:数据(属性)和操作数据的方法被捆绑在一起,封装在对象内部。你不用去记忆和操作一堆散乱的变量,而是直接对“书”这个整体对象下命令,代码的逻辑立刻变得清晰自然。
3. 不仅仅是实例:深入类方法与静态方法
到目前为止,我们接触的方法都需要先创建一个对象(实例),然后通过这个对象来调用,比如 my_novel.sell(68)。这类方法我们称之为实例方法,它们主要用来处理单个对象的数据。但有时候,我们需要一些功能,它和类相关,但又不需要(或不适合)操作某个具体的对象。Python 为我们提供了 类方法(Class Method) 和 静态方法(Static Method)。
先说说类方法。它的标志是使用装饰器 @classmethod,并且第一个参数不是 self,而是 cls,代表类本身。类方法最常见的用途是作为“替代构造器”,或者操作那些属于整个类(而不是单个对象)的属性(类属性)。
假设我们的书店对所有书都有一个统一的折扣率,这个折扣率不属于任何一本具体的书,而是书店的全局策略。我们可以把它定义为类属性,然后通过类方法来修改或应用它。
class BookStore:
# 类属性,所有对象共享
store_discount = 0.9 # 默认9折
total_books_sold = 0 # 书店售出的书的总数
def __init__(self, name, price):
self.name = name
self.base_price = price # 基础价格
def final_price(self):
"""计算折后价:实例方法,可以访问实例属性和类属性"""
return self.base_price * BookStore.store_discount
@classmethod
def change_discount(cls, new_discount):
"""类方法:修改整个书店的折扣"""
if 0 < new_discount <= 1:
cls.store_discount = new_discount
print(f"全店折扣已更新为:{new_discount}")
else:
print("折扣必须在0到1之间")
@classmethod
def book_sold(cls):
"""类方法:每卖出一本书,总数加1"""
cls.total_books_sold += 1
# 使用类方法,无需创建对象
BookStore.change_discount(0.8) # 直接通过类名调用
# 创建对象
book_a = BookStore("Python入门", 100)
book_b = BookStore("算法图解", 80)
# 卖书
book_a.book_sold() # 通过对象调用类方法,cls会自动传入BookStore类
book_b.book_sold()
print(f"书店售出书籍总数:{BookStore.total_books_sold}") # 输出:2
print(f"《{book_a.name}》折后价:{book_a.final_price()}") # 输出:80.0 (100*0.8)
再看静态方法。它使用装饰器 @staticmethod,既不需要 self 参数,也不需要 cls 参数。它就像一个被塞进类里的普通函数,仅仅是因为它在逻辑上与这个类相关,放在里面便于组织代码。静态方法不能访问或修改任何实例属性或类属性。
class BookStore:
store_discount = 0.9
@staticmethod
def calculate_tax(price, tax_rate=0.06):
"""静态方法:计算税费。与具体哪本书无关,只是一个计算工具。"""
return price * tax_rate
@staticmethod
def is_valid_isbn(isbn_str):
"""静态方法:简单校验ISBN格式(示例,非完整校验)"""
return len(isbn_str) == 13 and isbn_str.isdigit()
# 使用静态方法,同样无需对象
tax = BookStore.calculate_tax(100)
print(f"100元商品的税费是:{tax}元") # 输出:6.0
is_valid = BookStore.is_valid_isbn("9787111128069")
print(f"ISBN是否有效:{is_valid}") # 输出:True
提示:怎么快速区分这三种方法?问自己一个问题:这个方法需要知道是“谁”在调用它吗?如果需要操作或知道具体某本书的信息(如书名、价格),用实例方法。如果只需要知道是哪个书店(哪个类)的政策,用类方法。如果只是一个和书相关的通用工具函数,跟具体哪本书、哪个店都无关,用静态方法。
4. 代码的积木:模块化编程与类的导入
当你写的类越来越多,功能越来越复杂,把所有代码都堆在一个文件里会变得难以维护。想象一下,你把建造房子的所有图纸(类)、工具(函数)、材料(变量)都写在一张巨大的纸上,找起来得多崩溃。Python的模块化就是来解决这个问题的:把相关的代码分门别类地放到不同的文件(模块)里,然后用“导入”的方式像搭积木一样组合起来。
一个 .py 文件就是一个模块。我们可以把 Book 类和 BookStore 类分别保存到不同的文件中。假设我们有一个 book.py 文件,内容如下:
# book.py 文件
class Book:
def __init__(self, name, author):
self.name = name
self.author = author
def introduce(self):
return f"Book: {self.name} by {self.author}"
还有一个 bookstore.py 文件:
# bookstore.py 文件
class BookStore:
store_name = "Python书店"
@staticmethod
def welcome():
print(f"欢迎来到{BookStore.store_name}!")
现在,在另一个主程序文件 main.py 中,我们就可以像导入标准库一样导入我们自己写的模块和类了。导入方式非常灵活,常见的有以下几种:
方式一:导入整个模块
# main.py
import book
import bookstore
# 使用时需要带上模块名
my_book = book.Book("深入浅出Python", "Paul Barry")
print(my_book.introduce())
bookstore.BookStore.welcome()
方式二:从模块中导入特定的类或函数
# main.py
from book import Book
from bookstore import BookStore
# 现在可以直接使用类名,无需模块前缀
my_book = Book("Effective Python", "Brett Slatkin")
print(my_book.introduce())
BookStore.welcome()
方式三:导入模块并起别名(常用于模块名很长时)
# main.py
import bookstore as bs
from book import Book as Bk
my_book = Bk("Python Cookbook", "David Beazley")
bs.BookStore.welcome()
方式四:导入模块中的所有内容(不推荐,容易引起命名冲突)
# main.py
from book import *
from bookstore import *
# 可以直接使用,但如果有同名类或函数,后者会覆盖前者,很难调试
my_book = Book("Fluent Python", "Luciano Ramalho")
模块化带来的好处是巨大的。首先是代码复用,你写好一个通用的工具类,可以在无数个项目里 import 进来直接用,不用复制粘贴。其次是命名空间管理,不同模块里的同名函数和类不会打架。最后是结构清晰,大型项目可以按功能划分成多个包(Package,即包含 __init__.py 的文件夹)和模块,像 models/ 放数据类,utils/ 放工具函数,services/ 放业务逻辑,一目了然。
在实际项目中,你可能会遇到循环导入的问题(A模块导入了B,B又导入了A),这通常意味着你的代码结构需要优化。一个基本原则是:让依赖关系单向流动。高层模块可以依赖底层模块,但底层模块尽量不要依赖高层模块。如果确实需要共享某些东西,可以考虑把它们抽离到一个独立的、更基础的第三方模块中。
5. 实战:构建一个简易的图书馆管理系统
理论说了这么多,我们来个实战,把前面学的类、对象、方法、模块全部用上,构建一个超级简易但五脏俱全的“命令行图书馆管理系统”。我们会创建三个文件,模拟一个简单的模块化项目结构。
第一步:创建数据模型(models.py)
这个文件专门存放我们的核心类,代表系统中的“实体”。
# models.py
class Book:
"""图书类"""
def __init__(self, isbn, title, author, is_borrowed=False):
self.isbn = isbn # 国际标准书号,唯一标识
self.title = title # 书名
self.author = author # 作者
self.is_borrowed = is_borrowed # 是否被借出
def get_info(self):
status = "已借出" if self.is_borrowed else "在馆"
return f"ISBN: {self.isbn} | 《{self.title}》 - {self.author} | 状态: {status}"
def borrow(self):
if self.is_borrowed:
return f"《{self.title}》已被借出,无法再借。"
else:
self.is_borrowed = True
return f"成功借出《{self.title}》。"
def return_book(self):
if not self.is_borrowed:
return f"《{self.title}》本就在馆,无需归还。"
else:
self.is_borrowed = False
return f"感谢归还《{self.title}》。"
class Member:
"""会员类"""
def __init__(self, member_id, name):
self.member_id = member_id
self.name = name
self.borrowed_books = [] # 借阅的图书列表
def borrow_book(self, book):
if len(self.borrowed_books) >= 5: # 假设每人最多借5本
return f"{self.name},您借书已达上限。"
if book.is_borrowed:
return f"抱歉,《{book.title}》已被他人借走。"
result = book.borrow()
if "成功" in result:
self.borrowed_books.append(book)
return result
def return_book(self, book):
if book in self.borrowed_books:
result = book.return_book()
if "感谢归还" in result:
self.borrowed_books.remove(book)
return result
else:
return f"记录显示您并未借阅《{book.title}》。"
def show_my_books(self):
if not self.borrowed_books:
return f"{self.name},您当前没有借阅任何书籍。"
book_list = "\n".join([f" - {b.title}" for b in self.borrowed_books])
return f"{self.name}借阅的书籍:\n{book_list}"
第二步:创建图书馆管理类(library.py)
这个文件作为“服务层”,管理所有的书和会员,提供核心业务逻辑。
# library.py
from models import Book, Member
class Library:
"""图书馆核心管理类"""
def __init__(self, name):
self.name = name
self.books = {} # 用字典存储,key是ISBN,value是Book对象
self.members = {} # key是会员ID,value是Member对象
def add_book(self, isbn, title, author):
if isbn in self.books:
return f"ISBN为{isbn}的图书已存在。"
self.books[isbn] = Book(isbn, title, author)
return f"成功入库《{title}》。"
def register_member(self, member_id, name):
if member_id in self.members:
return f"会员ID {member_id} 已被注册。"
self.members[member_id] = Member(member_id, name)
return f"会员 {name} 注册成功。"
def find_book(self, keyword):
"""根据ISBN或书名关键词查找图书"""
results = []
for book in self.books.values():
if keyword == book.isbn or keyword.lower() in book.title.lower():
results.append(book)
return results
def show_all_books(self):
if not self.books:
return "图书馆暂无藏书。"
info_list = [book.get_info() for book in self.books.values()]
return "\n".join(info_list)
第三步:创建主程序入口(main.py)
这个文件是程序的启动点,负责与用户交互,调用前面模块的功能。
# main.py
from library import Library
def main():
# 初始化图书馆
my_lib = Library("Python数字图书馆")
# 预先添加一些图书和会员
my_lib.add_book("978-7-111-12806-9", "Python编程从入门到实践", "Eric Matthes")
my_lib.add_book("978-7-115-44559-3", "流畅的Python", "Luciano Ramalho")
my_lib.register_member("M001", "张三")
my_lib.register_member("M002", "李四")
while True:
print("\n=== Python数字图书馆管理系统 ===")
print("1. 显示所有图书")
print("2. 查找图书")
print("3. 借阅图书")
print("4. 退出")
choice = input("请选择操作 (1-4): ")
if choice == '1':
print("\n--- 馆藏列表 ---")
print(my_lib.show_all_books())
elif choice == '2':
keyword = input("请输入ISBN或书名关键词: ")
books = my_lib.find_book(keyword)
if books:
for b in books:
print(b.get_info())
else:
print("未找到相关图书。")
elif choice == '3':
# 这里为了简化,省略了会员登录和选择图书的详细交互
# 实际可以扩展:输入会员ID,查找图书ISBN,调用member.borrow_book(book)
print("借阅功能示例:")
member = my_lib.members.get("M001")
if member:
# 假设借阅第一本书
target_isbn = "978-7-111-12806-9"
target_book = my_lib.books.get(target_isbn)
if target_book:
print(member.borrow_book(target_book))
else:
print("图书不存在。")
else:
print("会员不存在。")
elif choice == '4':
print("感谢使用,再见!")
break
else:
print("输入无效,请重新选择。")
if __name__ == "__main__":
main()
运行 main.py,你就能看到一个在命令行中交互的简易图书馆系统了。这个项目虽然小,但完整地体现了面向对象编程的思想:Book 和 Member 是数据模型,封装了各自的状态和行为;Library 是管理类,协调多个对象之间的交互;main.py 是用户界面和程序入口。通过分文件(模块)组织,代码结构清晰,职责分明。如果你想增加新功能,比如图书分类、借阅期限、逾期罚款,只需要在相应的类里添加属性和方法,或者创建新的模块,而不会让原有代码变得混乱。这就是模块化、面向对象编程带来的可维护性和可扩展性。

3174

被折叠的 条评论
为什么被折叠?



