news 2026/9/11 12:35:46

Python面向对象一文精通:类、继承、多态与实战避坑指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Python面向对象一文精通:类、继承、多态与实战避坑指南

Python面向对象一文精通

先直接说结论:Python的面向对象(OOP)不是一套需要背下来的语法,而是一种组织代码的思维方式。我见过太多初学者卡在“类、对象、继承”这些抽象名词上,其实换个角度理解——你只是想把数据和操作数据的方法打包到一起,复用起来更方便。这篇文章不打算写成一本枯燥的教科书,而是按实际写项目时的思考路径来拆解Python面向对象:从环境搭建到类的设计,从魔法方法到工程实践中真正遇到的坑,一条线讲透。

这篇文章适合谁?如果你刚学完Python基础语法(变量、条件、循环、函数),正想弄明白“类到底是什么”,或者你已经写过一些脚本,但不太清楚什么时候该用面向对象、该怎样设计类,那这篇文章就是给你准备的。我会尽量用大白话,把底层原理和实操经验都揉进去,让新手能看懂、老手也能从中找到一些值得回味的细节。

1. 开始之前:先把环境这关过了

很多人一看到“面向对象”就直接跳进语法,结果在自己电脑上跑不通第一个示例代码,查了一圈发现是解释器版本或者编辑器配置的问题。所以我想先花一点时间,把环境搭建的常见坑说清楚,这会直接影响后面所有代码的演示效果。

1.1 解Python解释器安装:版本选3.8还是3.12

当前阶段我建议直接装Python 3.10以上的稳定版本,至少是3.9。为什么?因为面向对象相关的语法和标准库特性在3.9之后更完善,比如类型注解的泛型支持、字典合并运算符、更清晰的错误提示,都会让你的学习和调试顺畅很多。

安装路径有个容易踩的坑:Windows下安装时,如果你勾选了“Add Python to PATH”,后面在命令行里敲python有大概率直接用上。但如果你电脑里装了多个Python版本(比如系统自带3.8、后来又装了3.12),命令行里运行的很可能不是你想用的那个。我建议在安装时选择自定义路径,比如C:\Python312,然后把系统环境变量里的Path顺序调整一下。验证是否装对,打开终端敲python --version,再敲where python看看它指向哪里,两个对得上就说明没问题。

Linux下稍微简单一点,Ubuntu/Debian系用sudo apt install python3,RedHat/CentOS系用sudo yum install python3。不过系统自带的Python版本可能比较老,如果你想用新版本,我比较推荐使用pyenv来管理,它能让你在同一个系统里自由切换多个Python版本,不会把系统环境搞乱。

1.2 VSCode环境配置:少踩两个隐形坑

编辑器方面,VSCode是绝大多数人的选择,插件装好基本就是IDE体验。需要装的是Python官方插件(Python Extension Pack)。装完之后,打开命令面板(Ctrl+Shift+P),输入Python: Select Interpreter,选到你刚安装的解释器路径。

这里有个隐蔽的问题:如果你在VSCode里打开了多个项目文件夹,解释器选择可能会被全局覆盖,导致不同项目之间用错了环境。我习惯给每个项目添加一个.vscode/settings.json文件,显式写明解释器路径,这样团队协作或者自己换电脑后都能一键对齐。

另一个坑是Pylance的静态检查有时会误报。遇到红色的波浪线先别慌,鼠标悬停看具体提示,很多时候是类型推断的问题,不影响运行。如果你确认自己的代码没问题,可以把settings.json中的python.analysis.diagnosticMode从workspace改成openFiles,减少一些干扰。

2. 面向对象的核心:类、实例与方法

环境搞定了,咱们正式开始。面向对象这一套,核心只有三件事:类是模板,实例是具体对象,方法是对象的行为。

2.1 类是模板,实例是实体

我先拿做饭打个比方。你手里的食谱就是“类”,它定义了一道菜需要哪些食材(属性)、按什么步骤来做(方法)。而你今天实际操作时用的那一份具体的食材、那口锅,就是“实例”。每个食谱可以做无数次,但每一次做出来的具体菜都是独立的实例。

在Python里定义一个类非常简单:

class Dog: def __init__(self, name, age): self.name = name self.age = age def bark(self): print(f"{self.name} 叫了一声:汪汪!")

这里的Dog就是类名,__init__是初始化方法,它负责在创建实例时给实例设置初识状态。self代表了实例本身,你在类内部写的方法,第一个参数永远是self,这是Python和其他语言不一样的地方——它显式地把“当前对象”传进来。

创建实例更像这样:

d = Dog("旺财", 3) d.bark()

d是一个Dog类型的实例,它有自己独立的name和age。你再创建一个d2 = Dog("小黄", 1),d和d2互不干扰,这就体现了面向对象里“数据绑定”的概念。

2.2 初始化方法__init__与self

很多人刚接触__init__的时候会问:为什么不直接在类里写self.name = "xxx"呢?因为你可能在创建不同实例时想要不同的名字。__init__让你可以在实例化时传入参数,然后根据参数来初始化实例的属性。没有它,每个实例的初始状态都一样,那就失去了“类作为模板可以多变”的意义。

我补充一下self的底层逻辑:调用d.bark(),Python解释器其实会把它转成Dog.bark(d),所以self自动指向了d。你如果在类里写了一个不带self的方法,那它只是定义在类的一个普通函数,实例调用时会报错,因为这个函数没有被绑定。这一点在后面的类方法和静态方法里会有更深入的理解。

有一个细节新手容易忽略:init__并不是构造对象的真正入口。真正创建对象的是__new,它是先返回一个空实例,然后__init__再对这个实例做初始化。你平时写代码不用管__new__,但如果你做元类编程、单例模式或者不可变对象时,__new__才会派上用场。我后文会专门讲一段特殊方法。

3. 三大特性实战:封装、继承、多态

封装、继承、多态,是教科书里的三大金刚。我换个角度讲,让它们不再像考试名词,而是你写代码时能真正用到的手段。

3.1 封装——把数据关起来

封装的核心思想是:对象内部的数据不应该被外部随意修改,外部只能通过对象提供的公开方法来操作数据。我打个比方:ATM机的出钞口和键盘是公开接口,但内部的点钞机、验钞模块你是碰不到的,你只能通过合法的操作(插卡、输密码、按金额)来取钱。

在Python里,约定用下划线前缀表示“内部属性”:_name表示不要随便碰,__name则会被Python“名称修饰”处理,外部访问时需要改名字。

class BankAccount: def __init__(self, owner, balance): self.owner = owner self.__balance = balance def deposit(self, amount): if amount > 0: self.__balance += amount def withdraw(self, amount): if 0 < amount <= self.__balance: self.__balance -= amount else: print("余额不足") def get_balance(self): return self.__balance

这个例子中,你没办法直接从外面扣balance,因为它是__balance。你只能通过deposit和withdraw方法来操作。好处是显而易见的:你可以在方法里加入校验逻辑,避免非法操作把数据搞坏。

注意,Python的“私有”是“君子协定”而非强制。你使用dir(bank_account)能看到一个_类名__balance的属性名,理论上还是能访问的。Python设计哲学是“我们都是成年人”,它通过约定和命名让开发者自律,而不是用强制访问控制来限制你。

3.2 继承——父类代码最大化复用

继承解决的是“多个类有共同特征”时的代码重复问题。我做过一个宠物管理系统,宠物有猫、狗、鸟,它们都有name和age,都会speak,但发音完全不同。用继承来写:

class Pet: def __init__(self, name, age): self.name = name self.age = age def speak(self): raise NotImplementedError("子类需要实现这个方法") class Cat(Pet): def speak(self): print(f"{self.name}说:喵喵") class Dog(Pet): def speak(self): print(f"{self.name}说:汪汪")

这里Cat和Dog都继承自Pet,它们自动拥有name和age属性,只需实现自己与众不同的speak。父类的__init__可以被子类的实例调用,你可以通过super().init(name, age)来复用父类的初始化逻辑,而不必在子类里重复一遍:

class Bird(Pet): def __init__(self, name, age, color): super().__init__(name, age) self.color = color

继承的另一个现实收益是:如果公共逻辑有变动,比如Pet里要增加一个owner属性,你只需要改父类,所有子类自动获得新属性。这个维护成本节省是很可观的。

关于“要不要用继承”这个话题,我在后面设计思维部分会展开说。这里先记住一个关键判断标准:子类和父类之间必须有“is-a”关系(猫是一只宠物),如果只是“has-a”(宠物有一个主人),那应该用组合。

3.3 多态——同一个接口,多种行为

多态这个词最容易把人吓住,其实它描述的现象特别简单:同样是speak()方法,不同对象调用时,得到的结果不一样。

pets = [Cat("小花", 2), Dog("旺财", 3), Bird("啾啾", 1, "黄色")] for pet in pets: pet.speak()

运行结果分别输出各种动物的叫声。你并没有在循环里去判断pet是Cat还是Dog,Python自动根据对象的实际类型去调用对应的speak实现。这个能力让代码变得极其灵活,你后面扩展一个Fish类,只要它继承Pet并实现了speak,上面这段循环完全不用改。

多态的好处是“面向接口编程”。当你把不同对象放入同一个列表,并调用同一个方法时,你的高层逻辑不再依赖具体类型,而是依赖统一的接口约定。这就是设计模式里“开闭原则”的基础:对扩展开放,对修改封闭。

4. 魔法方法:为什么内置函数能和你的对象玩得转

Python里有一批双下划线开头和结尾的方法,比如__init__、lenstradd,统称“魔法方法”。它们是Python内置操作和你的类之间的桥梁。理解了这些方法,你写的对象就能无缝融入Python的语言生态。

4.1 最常见的魔法方法

class Vector: def __init__(self, x, y): self.x = x self.y = y def __str__(self): return f"Vector({self.x}, {self.y})" def __repr__(self): return f"Vector({self.x}, {self.y})" def __add__(self, other): return Vector(self.x + other.x, self.y + other.y) def __len__(self): return int((self.x ** 2 + self.y ** 2) ** 0.5)

__str__定义了一个对象被print时显示的字符串,__repr__定义了一个对象在交互式环境里直接显示时的字符串。__add__让+号可以用在你自己定义的类上,否则你只能调用v1.add(v2)这种别扭的写法。__len__让len()函数可以作用在你的对象上。

魔法方法的根本价值是:让自定义类拥有和内置类型一样的“语法待遇”。以后你写集合类、数据模型、工具库,都可以靠魔法方法提供更自然的调用体验。

我举一个真实场景:之前做数据统计库,定义了一个Result类,实现__add__后,我可以直接把两个实验结果用+号聚合,同事一眼就能看懂代码意图。如果不用魔法方法,得写result1.merge(result2),阅读成本和交流成本都会更高。

4.2 上下文管理器与with

with语句在Python里非常常用,比如with open(...) as f。它背后依赖的是两个魔法方法:enter__和__exit。你完全可以给自己的类也加上这对方法,让资源管理自动化。

class Database: def __enter__(self): print("连接数据库") return self def __exit__(self, exc_type, exc_val, exc_tb): print("关闭数据库连接") def query(self, sql): print(f"执行sql: {sql}") with Database() as db: db.query("select * from users")

即使with块内部抛异常,__exit__也会被调用,确保连接被释放。我建议所有涉及打开文件、连接网络、操作数据库的类,都尽量实现这对方法,它能防住一大半因忘记关闭资源导致的问题。

__exit__的三个参数(exc_type、exc_val、exc_tb)分别代表异常类型、异常值和traceback。如果你在__exit__里返回True,就会吞掉异常,外部不再报错;返回False或None则异常会继续向外抛出。这是一个很难但很有用的细节。

4.3 运算符重载

Python允许你为一个类重载大量运算符。除了上面提到的__add__,还有__sub__、muleqltgt__等。例如做一个分数类Fraction,你想让两个分数直接加,就必须实现__add;你想让两个分数作比较,就需要实现__lt__等比较方法。

class Fraction: def __init__(self, numerator, denominator): self.numerator = numerator self.denominator = denominator def __eq__(self, other): return self.numerator * other.denominator == other.numerator * self.denominator def __lt__(self, other): return self.numerator * other.denominator < other.numerator * self.denominator

实现之后,你就可以直接写a == b,a < b这样的表达式,而不需要调用a.equal_to(b)这类方法。Python内置的sorted()函数也能直接对Fraction对象列表排序,因为它依赖<比较逻辑。

5. 真正工程级的类设计和避坑清单

前面讲的都是基础知识,这里我总结一些在自己写代码和带团队时经常遇到的坑。每一个都踩过,分享出来帮你们少走弯路。

5.1 类变量和实例变量别搞混

这是最经典的坑,很多人都在这里翻车。看下面这个例子:

class Student: scores = [] # 类变量 def add_score(self, score): self.scores.append(score)

这里scores是类变量,它属于所有实例共享。你创建两个学生对象,给其中一个添加分数,另一个也能看到这个分数。这大概率不是你想要的效果。正确做法是在__init__里把scores定义为实例变量:

class Student: def __init__(self): self.scores = [] def add_score(self, score): self.scores.append(score)

判断标准很简单:如果变量描述的是所有实例共有的默认状态,才用类变量;只要每个实例需要独立数据,就必须在__init__里通过self.来定义。类变量偶尔有用,比如记录一个类总共创建了多少个实例,但更多时候它是bug的来源。

5.2 可变默认参数是个大坑

函数和方法的默认参数如果是可变对象,比如列表或字典,会有一个很隐蔽的问题:

def add_item(item, items=[]): items.append(item) return items

第一次调用add_item("a")返回["a"],第二次调用add_item("b")返回["a", "b"]。因为默认列表在函数定义时只创建一次,后续调用共用同一个列表。这个坑在面向对象里也很常见,比如构造器里写definit(self, tags=[])。解决办法是把默认值设为None,在函数内部再创建:

def add_item(item, items=None): if items is None: items = [] items.append(item) return items

这个坑背后的原理是Python函数的默认参数值在定义时计算一次,后续不会重新赋值。只要理解了这点,你就不会再为奇怪的“记忆”行为困惑。

5.3 继承时老老实实用super()

子类在重写父类方法时,如果需要调用父类的同名方法,我推荐用super()。我之前见过很多人在Python 2时代用父类名直接调用,比如Pet.init(self, name, age),这个方法在Python 3下也能跑,但一旦你涉及多重继承,它很容易打乱方法解析顺序(MRO),导致奇怪的连带调用。

class Bird(Pet): def __init__(self, name, age, color): super().__init__(name, age) # 推荐写法 self.color = color

super()不是简单调用父类,它会根据实例的MRO找到下一个应该调用的类。你只需要记住:写继承时,子类要复用父类逻辑,优先用super()而不是直接写父类名。这样在复杂继承场景下才不容易出错。

5.4 @property、slots、dataclass:三个提高效率的利器

@property让你把一个方法当成属性来用:

class Circle: def __init__(self, radius): self.radius = radius @property def area(self): return 3.14159 * self.radius ** 2 c = Circle(5) print(c.area) # 不是c.area()

这种写法的好处是:调用方不需要关心数据是存储的还是计算的。你以后如果把area改成存储的值,调用方的代码不用改。这符合封装的思路,也保持了接口的稳定性。

__slots__用来限制实例可以动态添加的属性,并减少内存占用。如果你的类有成百上千个实例(比如游戏里的粒子对象、棋盘的格子),给每个实例都动态维护一个字典会浪费不少内存,使用__slots__可以把这个开销降下来:

class Particle: __slots__ = ("x", "y", "vx", "vy") def __init__(self, x, y, vx, vy): self.x = x self.y = y self.vx = vx self.vy = vy

使用__slots__后,实例不再有__dict__,内存占用小很多,速度也有提升。代价是不能随意添加新属性,但这对数据模型明确的类来说是合理的限制。

dataclass是Python 3.7引入的标准库装饰器,它自动帮你生成__init__、repr、__eq__等方法,可以让数据模型的代码量减少一半:

from dataclasses import dataclass @dataclass class Point: x: int y: int

创建对象时Point(1, 2),打印它就显示Point(x=1, y=2),比较起来也自动按字段比较。日常开发中,只要是单纯承载数据的类,我第一个都用dataclass,而不是手动写一堆魔法方法。

6. 面向对象设计思维:什么时候该做类,怎么设计不别扭

很多人在语法上完全没问题,但一到实际项目就卡在“我到底该不该定义一个类”。这一章我讲讲设计判断标准。

6.1 从面向过程到面向对象的转变

面向过程的思路是:程序=数据结构+算法,你抽出一堆函数来操作数据。这种模式适合处理步骤固定的脚本,比如一次数据导入、一个定时任务。一旦逻辑复杂起来,数据分散在各个变量里,函数之间彼此依赖,维护就会变得痛苦。

面向对象的一个关键转变是:把数据和操作数据的函数绑在一起。你会发现很多函数天然属于某个数据实体。比如用户相关的操作(login、logout、change_password、update_profile),数据都是用户信息,这时定义一个User类就非常自然。函数之间共享数据通过self来完成,不再需要一堆全局变量或参数传来传去。

我自己的判断口诀是:先看数据,再看操作。如果一堆数据总是同时出现、又总被同一组函数修改,那就值得封装成一个类。如果一段逻辑只需要一个函数,不需要维护任何状态,那就老老实实写成模块级函数,没必要为了“面向对象”而强行造类。

6.2 组合和继承,优先组合

开头说了,继承的适用场景是“is-a”,但很多人容易滥用继承导致代码复杂难懂。举个例子,你要做一个汽车类,它有引擎和轮子。引擎和轮子不是汽车的形态,构造时应该用组合:

class Engine: def start(self): print("引擎启动") class Wheel: def roll(self): print("轮子滚动") class Car: def __init__(self): self.engine = Engine() self.wheels = [Wheel() for _ in range(4)]

如果强行用继承,写class Car(Engine, Wheel),不但在逻辑上很奇怪,而且会继承到很多用不上的方法。组合的另一个优势是容易替换组件:我把Car里的self.engine换成ElectricEngine,Car的代码不需要改。继承则很难做到这种运行时替换。

我补充一个常见的设计模式理论:里氏替换原则(LSP)。简单来说,子类对象应该能在不修改程序正确性的前提下,替换父类对象。如果你的子类重写了父类方法却改变了行为契约(比如父类方法返回int,子类却返回str),那这个继承关系就是不合理的。用组合能天然避开这类问题。

6.3 一个实战设计示例:商品订单系统

我来演示一次完整的设计思考过程。假设要做一个简单的电商订单打印系统,包含订单、商品、用户三个概念。

首先分析数据:

  • 用户:用户名、邮箱、地址
  • 商品:名称、价格、库存
  • 订单:订单号、用户、商品列表、总价

操作:

  • 用户能添加商品到订单
  • 订单能计算总价
  • 订单能生成描述文本

根据“数据和操作绑定”的思路,可以定义三个类:

@dataclass class User: username: str email: str address: str @dataclass class Product: name: str price: float stock: int class Order: def __init__(self, order_id, user): self.order_id = order_id self.user = user self.items = [] def add_product(self, product, quantity=1): if product.stock < quantity: raise ValueError(f"{product.name} 库存不足") self.items.append((product, quantity)) product.stock -= quantity def total_price(self): return sum(product.price * quantity for product, quantity in self.items) def summary(self): lines = [f"订单号:{self.order_id},用户:{self.user.username}"] for product, quantity in self.items: lines.append(f" {product.name} x {quantity} = {product.price * quantity:.2f}") lines.append(f"总价:{self.total_price():.2f}") return "\n".join(lines)

这里Order持有User和Product的引用,是组合关系。以后加一个Discount计算,只需要扩展Order或者新增策略类,不影响User和Product。我建议你用这个例子自己动手敲一遍,然后尝试增加一个“优惠券”功能,看怎么设计才合理。这种练习比死记语法有效得多。

7. 实战整合:一个记账小程序用到所有核心特性

最后我再来一个相对完整的实战,把前文的类、继承、封装、魔法方法、装饰器、模块组织都串起来。项目是个人记账小工具,支持多种账户类型,能存入记录并输出统计。

from abc import ABC, abstractmethod from datetime import datetime class Transaction: def __init__(self, amount, note=""): self.amount = amount self.note = note self.time = datetime.now() def __str__(self): return f"{self.time:%Y-%m-%d %H:%M} {self.note} {self.amount:+.2f}" class Account(ABC): def __init__(self, name, initial_balance=0): self.name = name self.balance = initial_balance self.transactions = [] def deposit(self, amount, note=""): if amount <= 0: raise ValueError("存款金额必须大于0") self.balance += amount self.transactions.append(Transaction(amount, note)) def withdraw(self, amount, note=""): if amount <= 0: raise ValueError("取款金额必须大于0") if amount > self.balance: raise ValueError("余额不足") self.balance -= amount self.transactions.append(Transaction(-amount, note)) @property def total_income(self): return sum(t.amount for t in self.transactions if t.amount > 0) @property def total_expense(self): return sum(-t.amount for t in self.transactions if t.amount < 0) @abstractmethod def summary(self): pass class CashAccount(Account): def summary(self): return f"现金账户[{self.name}] 余额:{self.balance:.2f}" class CreditCardAccount(Account): def __init__(self, name, credit_limit, initial_balance=0): super().__init__(name, initial_balance) self.credit_limit = credit_limit def withdraw(self, amount, note=""): if amount > self.balance + self.credit_limit: raise ValueError("超出信用额度") self.balance -= amount self.transactions.append(Transaction(-amount, note)) def summary(self): return f"信用卡[{self.name}] 可用额度:{self.balance + self.credit_limit:.2f}"

这段代码里出现了几个值得解释的地方:

第一,ABC和abstractmethod来自abc模块,它强制要求子类实现summary方法。凡是继承Account的类,如果没实现summary,实例化时就会报错。这比自己在方法里raise NotImplementedError更安全,因为错误在创建对象时就被拦截,而不是等到运行时才暴露。

第二,CreaditCardAccount里重写了withdraw逻辑,因为信用卡的取款逻辑和普通账户不同。它复用了父类的deposit,并调用了super().__init__来初始化公共属性,体现继承和多态的正确用法。

第三,我把Transaction设计成不可变的数据对象,通过__str__魔法方法让打印变得更直观。Account的total_income和total_expense用@property动态计算,不需要额外维护总和的字段,数据一致性更好。

实际使用时:

cash = CashAccount("我的钱包", 1000) credit = CreditCardAccount("信用卡", 5000) cash.deposit(500, "工资") cash.withdraw(200, "买菜") credit.withdraw(300, "商场购物") for acct in [cash, credit]: print(acct.summary())

打印结果里能看到两类账户都记录了自己的流水,并且因为Transaction重写了__str__,你只需再写一行代码就可以列出所有流水。这就是面向对象在真实项目里的体验:核心逻辑写在类里,使用起来像拼积木一样清晰。

如果把这个项目再扩展,你可以给Transaction增加category属性,在Account里实现按分类统计。也可以用dataclass改造Transaction,减少模板代码。后面的路就顺理成章了。

8. 常见问题与排查技巧实录

我整理了一份高频问题速查表,这些问题在我带过的学员、以及自己早期写代码时都反复出现。你可以把它当作一份参考手册,遇到问题直接翻这里。

报错或异常原因解决方案
TypeError: Student() takes no arguments构造函数名写错了,通常是拼成init而不是__init__检查__init__是否双下划线且拼写正确
TypeError: bark() takes 1 positional argument but 2 were given实例方法缺少self参数在类方法定义时加上self作为第一个参数
AttributeError: 'Dog' object has no attribute 'x'属性x没有在__init__或其它方法里定义检查实例变量是否初始化,or直接调用位置是否写错
NameError: name 'self' is not definedself是作为参数传入的,不是全局可用确保在方法内部访问实例属性时写self.xxx
继承了父类但子类无法生成父类使用了abc.ABC并定义了abstractmethod在子类中实现所有抽象方法
同一个列表被所有实例共享类变量误用,而不是实例变量把可变对象放回__init__里初始化
super()报错多重继承时MRO冲突,或super()放在错误位置尽量保持MRO清晰,不要写菱形继承,必要时打印类.mro()检查
打印对象时出现<main.X object at 0x...>未实现__str__或__repr__给类添加__repr__,调试信息更直观

经验提示:遇到AttributeError时,先用dir(instance)查看实例有哪些属性和方法,再用type(instance)确认类型。这两条命令可以解决一半的类相关报错。不要靠猜,直接把对象内部情况看一遍,问题通常一目了然。

另外一个很有效但常被忽略的技巧:在调试魔法方法时,用pdb或者直接在方法里print("called", ...)来判断它到底有没有被调用。比如你觉得__init__没执行,就在__init__第一行加个print("init called"),运行一遍看结果。这个方法虽然朴素,但往往比翻文档更直接。

最后说点实在的

从我自己的经验看,面向对象不是学完语法就会了,而是在多次重构和扩展中慢慢形成感觉。我一开始也写过一堆毫无必要、强行用类包裹的代码,后来随着对业务理解加深,才逐渐明白什么时候该抽象、什么时候该保持简单。

如果你能把这篇文章里的代码都敲一遍,按我建议的方式亲手实践几次,再把那个记帐小工具自己扩展几个功能,你基本就已经掌握Python面向对象的绝大多数套路了。后面的路还很长,但核心的地基在这里。写代码的时候遇到什么卡住的地方,想想“数据和操作是否应该绑定在一起”,往往就能找到方向。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/11 12:33:54

从功率训练到智能骑行台:迈金生态设备配置与顽鹿竞技实战指南

如果你最近两年开始认真骑公路车&#xff0c;或者是铁三爱好者&#xff0c;那大概率绕不开两个词&#xff1a;功率训练和虚拟骑行。我自己折腾设备这两年&#xff0c;感触最深的是&#xff0c;国内这套骑行智能硬件生态里&#xff0c;迈金科技几乎把从入门到进阶的坑都补得差不…

作者头像 李华
网站建设 2026/9/11 12:33:11

Flink SQL + Kafka 实时统计实战:从建表、窗口聚合到踩坑排查

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/11 12:32:07

五大AI高薪能力域:从MVP交付到岗位入场的实战拆解

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/11 12:31:30

Python时间序列预测:ARIMA模型从定阶到预测实战全解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/11 12:31:15

Refine 实战:useList 排序(sorters)与动态数据列表构建指南

Refine 实战&#xff1a;useList 排序&#xff08;sorters&#xff09;与动态数据列表构建指南 【免费下载链接】refine A React Framework for building internal tools, admin panels, dashboards & B2B apps with unmatched flexibility. 项目地址: https://gitcode.c…

作者头像 李华
网站建设 2026/9/11 12:30:27

NPU/GPGPU乱序执行设计:面向AI负载的粗粒度OoO实践

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华