Python 类和对象:带有实际示例的 OOP 教程

  1. 首页
  2. 博客
  3. 软件开发

软件开发

Python 类和对象:OOP 教程与实例

通过构建真实的东西来学习面向对象的 Python。创建类,了解 self 和 __init__,通过继承共享行为,通过属性保护数据,并了解类何时是错误的工具。

阅读约 10 分钟
Python 类和对象:带有真实示例的 OOP 教程

要点速览

  • 类是一个蓝图,对象是由它构建的东西。
  • __init__ 设置每个新对象,self 引用该对象。
  • 类上设置的属性是共享的。 self 上设置的属性属于一个对象。
  • 将 @dataclass 用于数据保存类,并且在没有要保存的状态时更喜欢简单的函数。

随着脚本的增长,您最终会在函数之间传递同一组变量,并且很难区分哪些数据属于哪个逻辑。 Python 类和对象 通过将数据和行为捆绑到一个单元中来解决这一问题。

在本教程中,我们将逐步构建一个小型银行帐户,并且仅在每个 OOP 想法解决问题时才添加它。我们在 Python 3.13 上运行了每个示例,显示的输出来自该运行。如果您不熟悉该语言,请先阅读我们的 Python 初学者教程。

什么是类和对象?

类描述了某事物是什么以及它可以做什么。 对象,也称为实例,是根据该描述创建的特定对象。 “Account”是一个类,而Asha的帐户和Ben的帐户是两个对象。

期限 含义 示例
班级 蓝图 账户
对象或实例 由它制成的一件东西 a = 账户(“阿莎”)
属性 对象上的数据 a.balance
方法 类上的一个函数 a.deposit(50)

你一直在使用对象。在Python中,字符串、列表甚至函数都是对象,这就是你可以编写"hello".upper()的原因。

你的第一堂课

class Account:
    def __init__(self, owner, balance=0):
        self.owner = owner
        self.balance = balance

    def deposit(self, amount):
        if amount <= 0:
            raise ValueError("amount must be positive")
        self.balance += amount
        return self.balance

a = Account("Asha", 100)
b = Account("Ben")

a.deposit(50)
print(a.balance, b.balance)

# 150 0

__init__ 每当您创建对象时都会自动运行,并设置起始状态。存入a并没有触及b,因为每个对象都有自己的数据。请注意,该方法还可以保护余额免受错误值的影响。我们的 Python 异常处理 指南更深入地解释了 raise。

什么是自我?

self 只是方法正在处理的对象。当您编写 a.deposit(50) 时,Python 会有效地调用 Account.deposit(a, 50),并将对象作为第一个参数传入。这就是一个方法定义可以服务于每个对象的方式。

名称 self 是一个强约定而不是关键字,但始终使用它。在方法定义中忘记它是初学者最常见的错误,并且它会产生有关参数数量的令人困惑的消息。

类属性与实例属性

直接在类主体中设置的属性由所有对象共享。 self 上的一组仅属于该对象。此示例显示查找的行为方式:

class Account:
    interest_rate = 0.04    # class attribute

    def __init__(self, owner, balance=0):
        self.owner = owner  # instance attribute
        self.balance = balance

a = Account("Asha", 100)
b = Account("Ben")
print(a.interest_rate, b.interest_rate)   # 0.04 0.04

Account.interest_rate = 0.05
print(a.interest_rate, b.interest_rate)   # 0.05 0.05

a.interest_rate = 0.09
print(a.interest_rate, b.interest_rate, Account.interest_rate)
# 0.09 0.05 0.05

最后一行让人们感到惊讶。通过a分配并没有改变共享值。它在 a 上创建了一个新的实例属性,隐藏了类 1。 Python 首先查看对象,然后查看类。

特殊(dunder)方法

带有双下划线的方法使您的对象可以使用 Python 的内置功能。如果没有它们,打印帐户将显示无用的内存地址。添加两个使其成为可读的形式:

    def __str__(self):
        return f"{self.owner}: {self.balance}"

    def __repr__(self):
        return f"Account({self.owner!r}, {self.balance})"

print(a)        # Asha: 150
print([a, b])   # [Account('Asha', 150), Account('Ben', 0)]
方法 触发于
__init__ 创建一个对象
__str__ print(obj), str(obj)
__repr__ 调试,或在列表中显示
__eq__ a==b
__len__ len(obj)
__add__ a+b

继承

继承允许新类重用现有类并仅更改不同的部分。这里,Savings 是带有额外设置的 Account:

class Savings(Account):
    def __init__(self, owner, balance=0, limit=3):
        super().__init__(owner, balance)
        self.limit = limit

    def __str__(self):
        return f"{super().__str__()} (savings)"

s = Savings("Chitra", 500)
s.deposit(100)
print(s, isinstance(s, Account), s.limit)

# Chitra: 600 (savings) True 3

super()调用父版本,因此不重复设置逻辑。 deposit 按原样继承,__str__ 被覆盖以添加标签。仅将继承用于真正的“is a”关系。如果一个类仅需要某些东西,例如汽车需要引擎,则将该对象作为属性提供给它。这称为组合,它通常比深度继承链更灵活。

多态性

多态性意味着不同的对象可以以自己的方式响应相同的方法调用。调用代码不需要知道确切的类型:

class Square:
    def __init__(self, side):
        self.side = side
    def area(self):
        return self.side ** 2

class Circle:
    def __init__(self, radius):
        self.radius = radius
    def area(self):
        return round(3.14159 * self.radius ** 2, 2)

for shape in [Square(3), Circle(2)]:
    print(type(shape).__name__, shape.area())

# Square 9
# Circle 12.57

这两个类甚至不共享父类。 Python 只关心对象是否有一个 area 方法,这种方法称为 ducktyping。稍后添加 Triangle 不需要更改循环。

封装和特性

Python 没有真正的私有属性,而是依赖于约定。单个下划线,如 _price 中所示,告诉其他开发人员“这是内部的,请不要触摸”。要在设置值时检查值,请使用 property,它从外部看起来像一个普通属性:

class Product:
    def __init__(self, price):
        self.price = price

    @property
    def price(self):
        return self._price

    @price.setter
    def price(self, value):
        if value < 0:
            raise ValueError("price cannot be negative")
        self._price = value

p = Product(100)
p.price = 150
print(p.price)      # 150
p.price = -5        # ValueError: price cannot be negative

这意味着您可以从普通属性开始,然后添加验证,而无需更改使用该类的任何代码。因此,不要像在 Java 中那样出于习惯而编写 get_price() 和 set_price() 方法。这里的@语法是一个装饰器,我们在Python装饰器解释中对此进行了解释。

类方法和静态方法

class Temperature:
    def __init__(self, celsius):
        self.celsius = celsius

    @classmethod
    def from_fahrenheit(cls, f):
        return cls(round((f - 32) * 5 / 9, 1))

    @staticmethod
    def is_freezing(celsius):
        return celsius <= 0

Temperature.from_fahrenheit(212).celsius   # 100.0
Temperature.is_freezing(-3)                # True
类型 第一个论点 用它来
实例方法 自我 使用一个对象的数据
@classmethod CLS 替代构造函数
@staticmethod 无 与班级相关的帮手

数据类

当一个类主要保存数据时,手写__init__、__repr__和__eq__是很乏味的。 @dataclass 从类型注释生成它们:

from dataclasses import dataclass, field

@dataclass
class Item:
    name: str
    price: int
    tags: list = field(default_factory=list)

print(Item("Pen", 10))
print(Item("Pen", 10) == Item("Pen", 10))

# Item(name='Pen', price=10, tags=[])
# True

提示:并不是所有东西都需要类。如果它没有状态并且只有一种行为,那么普通函数会更简单。对于简单的查找,字典可能就足够了。当数据和保护数据的规则明显属于同一类时,就可以选择一个类别。

常见错误

使用可变类属性

这是一个经典的错误。该列表是在类上定义的,因此所有购物车都共享它:

class Cart:
    items = []            # shared by every cart!

    def add(self, item):
        self.items.append(item)

c1, c2 = Cart(), Cart()
c1.add("pen")
print(c2.items)

# ['pen']

在 __init__ 和 self.items = [] 中创建它,以便每个对象都有自己的列表。

其他常见错误

  • 忘记self。在方法内部编写self.balance,而不是balance。
  • 忘记super().__init__()。 子级会错过父级设置的属性。
  • 构建庞大的类。 一个包揽一切的类很难测试。按责任划分。
  • 深度继承。 两个级别通常就足够了,并且组合通常更清晰。
  • 不编写测试。带有规则的类很容易测试,正如我们的pytest教程所示,使用小推车。

最后的话

面向对象的 Python 归根结底是将数据与保护数据的行为进行分组。从 __init__、方法和 __str__ 开始,然后在遇到它们解决的问题时添加继承和属性。下一步,采用一个脚本,在各处传递相同的几个变量,并将它们变成一个小类。

常见问题

类和对象有什么区别?

类是定义或蓝图。对象是由它创建的特定实例,具有自己的数据。一个类可以创建多个对象。

Python 中的 __init__ 是做什么的?

它是初始化程序,在创建对象时自动运行。它通常根据您传递的参数设置对象的起始属性。

Python 中的 self 是什么?

self 指的是调用方法的对象。 Python 自动传递它,并让该方法读取并更改该对象的属性。

OOP 的四大支柱是什么?

它们是封装、抽象、继承和多态。它们共同帮助您将数据与行为捆绑在一起、隐藏细节并重用代码。

什么时候应该使用类而不是函数?

当数据和行为属于多个操作时,请使用类。对于无状态操作,普通函数更简单且更易于测试。

继续学习

与Stack Quora合作

需要测试、WordPress 或 SEO 方面的帮助?

我们帮助团队交付更好的软件并发展他们的网站。告诉我们您正在做什么,我们将建议明确的下一步。

继续阅读

更多指南 测试、人工智能和现代开发

喜欢这篇文章吗?选择一个主题并直接跳至我们最新的教程。

人工智能

人工智能幻觉解释:语言模型为何会产生幻觉以及如何减少它

人工智能幻觉解释:它们是什么,为什么语言模型产生它们,常见类型,如何通过基础和提示来减少它们,以及我们进行的简单检查。

阅读文章 →
DevOps

环境变量解释:.env 文件、秘密和配置正确完成

使用我们运行的 Python 示例解释环境变量:读取和设置它们、为什么每个值都是字符串、.env 文件、进程继承、秘密和验证。

阅读文章 →
软件开发

API 速率限制解释:算法、标头和处理 429 错误

API 速率限制通过我们运行的模拟进行了解释:令牌桶、固定窗口、滑动窗口、429 响应、速率限制标头、带回退的重试和测试限制。

阅读文章 →
测试自动化

Python 中的模拟:unittest.mock、补丁以及导致测试谎言的错误

使用unittest.mock 在Python 中进行模拟,经过测试:模拟和return_value、side_effect、修补使用名称的位置、spec 和autospec、模拟日期以及常见错误。

阅读文章 →
软件测试

代码覆盖率解释:行覆盖率与分支覆盖率以及 100% 的真正含义

用真实的coverage.py运行来解释代码覆盖率:行、分支和条件覆盖率,阅读报告,为什么100%覆盖率仍然会错过错误,以及合理的目标。

阅读文章 →
数据库与 SQL

SQL 中的 DELETE、TRUNCATE 和 DROP:差异、回滚以及何时使用它们

DELETE、TRUNCATE 和 DROP 通过经过测试的 SQL 进行了解释:各自删除什么、回滚、触发器、身份重置、速度,以及 PostgreSQL、MySQL、SQL Server 和 SQLite 有何不同。

阅读文章 →
数据库与 SQL

SQL 视图解释:创建、使用和避免陷阱(附示例)

SQL 视图通过我们运行的查询进行解释:创建视图、实时结果、隐藏列、性能、可更新和物化视图、破坏的依赖关系和最佳实践。

阅读文章 →
软件开发

TypeScript 接口与类型:真正的差异,使用编译器进行测试

TypeScript 接口与类型,使用真实编译器进行检查:两者都可以做什么,只有一个可以做什么,声明合并,扩展与交集以及要遵循的简单规则。

阅读文章 →
软件开发

Python *args 和 **kwargs 解释:灵活的函数和清晰的示例

Python *args 和 **kwargs 用我们运行的代码进行了解释:打包和解包、参数顺序、仅关键字和仅位置参数、装饰器和常见错误。

阅读文章 →
软件开发

Python Lambda 函数:语法、实际用途以及何时不使用它们

Python lambda 函数用我们运行的代码进行了解释:语法、使用键排序、映射和过滤器、闭包、后期绑定陷阱,以及何时普通 def 是更好的选择。

阅读文章 →
人工智能

什么是人工智能代理?它们是如何工作的,只需运行一个简单的代理循环

什么是人工智能代理?关于代理循环、工具、内存和护栏的简单指南,以及我们运行的一个小型 Python 循环、真正的风险以及何时简单的聊天机器人是更好的选择。

阅读文章 →
DevOps

监控与可观察性:真正的区别,包括日志、指标和跟踪

监控与可观察性解释:已知和未知的故障、日志、指标和跟踪、检查事件演练、黄金信号、OpenTelemetry 和有用的警报。

阅读文章 →
DevOps

Docker Volumes 与 Bind Mounts:以正确的方式保留容器数据

Docker 卷与绑定挂载解释:为什么容器数据消失、命名卷、绑定挂载、tmpfs、Compose 示例、备份、权限以及何时使用它们。

阅读文章 →
测试自动化

TDD 与 BDD:主要区别、以两种方式构建的一个功能以及何时使用每种方式

TDD 与 BDD 的解释是通过一种双向构建的折扣功能:使用我们运行的代码进行红绿重构、Given-When-Then 场景、工具、权衡以及何时使用每种方案。

阅读文章 →
软件测试

用户验收测试 (UAT):流程、清单和真实示例

用户验收测试解释:谁运行它、它与 QA 有何不同、七步流程、UAT 测试用例模板、清单、签核标准和常见错误。

阅读文章 →
数据库与 SQL

SQL NULL 值解释:IS NULL、COALESCE、NULLIF 和要避免的陷阱

通过我们运行的查询解释了 SQL NULL 值:三值逻辑、IS NULL、COALESCE、NULLIF,聚合、连接和排序如何处理 NULL,以及它悄悄引起的错误。

阅读文章 →
数据库与 SQL

SQL CASE WHEN 语句:语法、实例和常见错误

SQL CASE WHEN 解释了我们运行的查询:简单和搜索表单、条件顺序、SUM 透视、COUNT 技巧、自定义排序、更新和要避免的 NULL 陷阱。

阅读文章 →
软件开发

什么是智威汤逊?通过工作示例解释 JSON Web 令牌

什么是智威汤逊? JSON Web Tokens 解释:这三个部分,在我们运行的代码中构建和篡改的令牌,JWT 与会话,存储,刷新令牌和安全错误。

阅读文章 →
软件开发

JavaScript 事件循环解释:调用堆栈、微任务和计时器以及示例

JavaScript 事件循环用我们运行的代码进行了解释:调用堆栈、任务和微任务队列、为什么 setTimeout 0 不是立即执行、阻塞、饥饿和面试测验。

阅读文章 →
软件开发

Python 生成器和 Yield 解释:通过真实示例节省内存

Python 生成器和yield 用我们运行的代码进行了解释:它们如何暂停和恢复、测量内存节省、管道、无限序列、yield from、发送和常见陷阱。

阅读文章 →
人工智能

什么是嵌入?向量和余弦相似度简单解释

什么是嵌入?关于向量、余弦相似度和语义搜索的简单语言指南,其中包含我们运行的一个小示例、常见用途、选择模型和已知限制。

阅读文章 →
DevOps

蓝绿部署与金丝雀部署:差异、权衡以及如何选择

蓝绿部署与金丝雀部署解释:每种部署的工作原理、成本、风险和回滚、我们测试的稳定用户分桶、数据库迁移、滚动更新以及如何选择。

阅读文章 →
测试自动化

不稳定的测试:造成这些问题的原因以及如何彻底解决它们

我们运行的演示解释了不稳定的测试:主要原因、如何确认和诊断、正确的时间修复、共享状态和日期以及隔离过程。

阅读文章 →
软件测试

负载测试与压力测试:差异、指标和 k6 示例

负载测试与压力测试的解释:各自证明什么、峰值和浸泡测试、重要的指标、为什么平均值会误导、k6 个示例以及如何计划测试。

阅读文章 →
软件测试

测试用例与测试场景:主要区别、示例以及何时使用它们

测试用例与测试场景的解释:清晰的定义、并排的表格、从场景到案例的密码重置功能、模板以及何时使用每个功能。

阅读文章 →
数据库与 SQL

SQL 查询优化:读取执行计划并修复慢速查询(带计时)

对一百万行进行 SQL 查询优化测试:读取执行计划、添加正确的索引、修复不可控制的过滤器、使用复合索引和键集分页。

阅读文章 →
数据库与 SQL

SQL UNION 与 UNION ALL:差异、示例和性能测试

SQL UNION 与 UNION ALL 通过我们运行的查询进行了解释:重复、都遵循的规则、真实的数据错误、测量的性能、INTERSECT 和 EXCEPT 以及何时使用它们。

阅读文章 →
软件开发

JavaScript Fetch API 教程:GET、POST、错误处理和超时

针对本地服务器测试的 JavaScript Fetch API 教程:GET 和 POST、为什么不抛出 404、可重用的帮助程序、查询字符串、超时、CORS 和常见错误。

阅读文章 →
软件开发

Python 文件处理:读取、写入、CSV、JSON 和 pathlib 以及示例

使用我们运行的代码解释了 Python 文件处理:打开模式、使用、读取大文件、CSV、JSON、pathlib、二进制文件、安全写入、错误处理和常见错误。

阅读文章 →
软件开发

Python 类和对象:带有实际示例的 OOP 教程

用我们运行的代码解释 Python 类和对象:__init__、方法、类与实例属性、继承、属性、dunder 方法、数据类和常见错误。

阅读文章 →
人工智能

什么是大语言模型?法学硕士如何运作,在没有炒作的情况下进行解释

什么是大语言模型?法学硕士用简单的语言解释:标记、嵌入、注意力、训练、温度和上下文窗口、一个微小的下一个单词演示和真正的限制。

阅读文章 →
DevOps

适合初学者的 Linux 命令:带有示例的基本终端指南

适合初学者的 Linux 命令,按任务分组:导航、文件、查看日志、grep、管道、权限、进程和档案,并附有实用的日志分析示例。

阅读文章 →
测试自动化

Selenium 与 Playwright:主要区别、代码比较以及选择

Selenium 与 Playwright 比较:架构、等待、浏览器和语言支持、调试、两种工具中的相同测试、迁移建议以及如何选择。

阅读文章 →
软件测试

等价划分:通过实例解释测试设计技术

等效分区解释:如何找到有效和无效分区,年龄、密码和优惠券的工作示例,我们运行的测试,以及为什么将其与边界配对。

阅读文章 →
软件测试

软件测试中的严重性与优先级:差异、矩阵和实例

软件测试中的严重性与优先级解释:清晰的定义、带有示例的四向矩阵、级别尺度、谁设置每个级别以及如何运行错误分类。

阅读文章 →
数据库与 SQL

SQL 子查询教程:类型、相关子查询和 EXISTS 以及示例

SQL 子查询教程,其中包含我们运行的查询:标量、IN、FROM 和 SELECT 子查询、相关子查询、EXISTS、NOT IN NULL 陷阱以及子查询与 JOIN 与 CTE。

阅读文章 →
数据库与 SQL

SQL 事务和 ACID 解释:COMMIT、ROLLBACK 和隔离级别

SQL 事务和 ACID 通过我们运行的银行转账进行了解释:COMMIT、ROLLBACK、保存点、锁、隔离级别、脏读、死锁和最佳实践。

阅读文章 →
软件开发

Git merge 与 rebase 的区别:示例及如何选择

用真实的 log 输出、对比表、rebase 黄金法则和安全的团队工作流,讲清 Git merge 与 rebase 的区别。

阅读文章 →
软件开发

Python 列表推导式:语法、示例与何时避免使用

Python 列表推导式详解:语法、过滤、if-else、嵌套循环、实测耗时,以及何时普通 for 循环更合适。

阅读文章 →
数据库与 SQL

SQL JOIN 详解:附示例(INNER、LEFT、RIGHT、FULL)

通过示例表和查询,一步步讲解 SQL JOIN:INNER、LEFT、RIGHT、FULL、CROSS 和 SELF,并介绍常见错误与性能技巧。

阅读文章 →