对象是如何表示的

PythonPythonBeginner
立即练习

This tutorial is from open-source community. Access the source code

💡 本教程由 AI 辅助翻译自英文原版。如需查看原文,您可以 切换至英文原版

简介

在这个实验中,你将学习 Python 对象在内部是如何表示的,并理解属性赋值和查找的机制。这些概念是掌握 Python 如何管理对象内的数据和行为的基础。

此外,你将探索类和实例之间的关系,并研究类定义在面向对象编程中的作用。这些知识将加深你对 Python 面向对象特性的理解。


Skills Graph

%%%%{init: {'theme':'neutral'}}%%%% flowchart RL python(("Python")) -.-> python/ObjectOrientedProgrammingGroup(["Object-Oriented Programming"]) python/ObjectOrientedProgrammingGroup -.-> python/classes_objects("Classes and Objects") python/ObjectOrientedProgrammingGroup -.-> python/constructor("Constructor") subgraph Lab Skills python/classes_objects -.-> lab-132499{{"对象是如何表示的"}} python/constructor -.-> lab-132499{{"对象是如何表示的"}} end

创建一个简单的股票类

在这一步中,我们将创建一个简单的类来表示股票。理解如何创建类是 Python 编程的基础,因为它使我们能够对现实世界的对象及其行为进行建模。这个简单的股票类将是我们探索 Python 对象内部工作原理的起点。

首先,你需要打开一个 Python 交互式 shell。Python 交互式 shell 是试验 Python 代码的好地方。你可以逐个输入并执行 Python 命令。要打开它,请在终端中输入以下命令:

python3

输入命令后,你会看到 Python 提示符 (>>>)。这表明你现在已进入 Python 交互式 shell,可以开始编写 Python 代码了。

现在,让我们定义一个 SimpleStock 类。Python 中的类就像是创建对象的蓝图。它定义了该类对象将具有的属性(数据)和方法(函数)。以下是如何定义具有必要属性和方法的 SimpleStock 类:

>>> class SimpleStock:
...     def __init__(self, name, shares, price):
...         self.name = name
...         self.shares = shares
...         self.price = price
...     def cost(self):
...         return self.shares * self.price
...

在上面的代码中,__init__ 方法是 Python 类中的一个特殊方法。它被称为构造函数,用于在创建对象时初始化对象的属性。self 参数指的是正在创建的类的实例。cost 方法通过将股票数量乘以每股价格来计算股票的总成本。

定义类之后,我们可以创建 SimpleStock 类的实例。实例是根据类蓝图创建的实际对象。让我们创建两个实例来表示不同的股票:

>>> goog = SimpleStock('GOOG', 100, 490.10)
>>> ibm = SimpleStock('IBM', 50, 91.23)

这些实例分别表示 100 股每股价格为 490.10 美元的谷歌股票和 50 股每股价格为 91.23 美元的 IBM 股票。每个实例都有自己的一组属性值。

让我们验证一下我们的实例是否正常工作。你可以通过检查它们的属性并计算它们的成本来完成。这将帮助我们确认类及其方法是否按预期运行。

>>> goog.name
'GOOG'
>>> goog.shares
100
>>> goog.price
490.1
>>> goog.cost()
49010.0
>>> ibm.cost()
4561.5

cost() 方法将股票数量乘以每股价格来计算持有这些股票的总成本。通过运行这些命令,我们可以看到实例具有正确的属性值,并且 cost 方法能够准确计算成本。

探索对象的内部字典

在 Python 中,对象是一个基本概念。可以将对象视为一个存储数据并具有特定行为的容器。Python 对象的一个有趣之处在于它们如何存储其属性。属性就像是属于对象的变量。Python 将这些属性存储在一个特殊的字典中,该字典可以通过 __dict__ 属性访问。这个字典是对象的内部组成部分,Python 会在其中记录与该对象相关的所有数据。

让我们使用 SimpleStock 实例来更深入地了解这个内部结构。在 Python 中,实例是从类创建的单个对象。例如,如果 SimpleStock 是一个类,那么 googibm 就是该类的实例。

要查看这些实例的内部字典,你可以使用 Python 交互式 shell。Python 交互式 shell 是快速测试代码并查看结果的好工具。在 Python 交互式 shell 中,输入以下命令来检查我们实例的 __dict__ 属性:

>>> goog.__dict__
{'name': 'GOOG', 'shares': 100, 'price': 490.1}
>>> ibm.__dict__
{'name': 'IBM', 'shares': 50, 'price': 91.23}

当你运行这些命令时,输出显示每个实例都有自己的内部字典。这个字典包含了所有的实例属性。例如,在 goog 实例中,属性 namesharesprice 及其对应的值都存储在字典中。这就是 Python 在幕后实现对象属性的方式。每个对象都有一个私有字典来保存其所有属性。

理解 __dict__ 属性揭示的对象内部实现细节很重要:

  1. 字典中的键对应于属性名。例如,在 goog 实例中,键 'name' 对应于对象的 name 属性。
  2. 字典中的值是属性值。因此,值 'GOOG'goog 实例的 name 属性的值。
  3. 每个实例都有自己独立的 __dict__。这意味着一个实例的属性与另一个实例的属性是相互独立的。例如,goog 实例的 shares 属性可以与 ibm 实例的 shares 属性不同。

这种基于字典的方法使 Python 在处理对象时具有很大的灵活性。正如我们将在下一步中看到的,我们可以利用这种灵活性以各种方式修改和访问对象属性。

添加和修改对象属性

在 Python 中,对象是基于字典实现的。这种实现方式让 Python 在处理对象属性时具有高度的灵活性。与其他一些编程语言不同,Python 并不将对象的属性局限于类中定义的那些。这意味着你可以在任何时候为对象添加新的属性,即使对象已经创建完成。

让我们通过为其中一个实例添加新属性来探索这种灵活性。假设我们有一个名为 goog 的类实例。我们将为它添加一个 date 属性:

>>> goog.date = "6/11/2007"
>>> goog.__dict__
{'name': 'GOOG', 'shares': 100, 'price': 490.1, 'date': '6/11/2007'}

在这里,我们为 goog 实例添加了一个新属性 date。注意,这个 date 属性并未在 SimpleStock 类中定义。这个新属性仅存在于 goog 实例上。为了确认这一点,让我们检查一下 ibm 实例:

>>> ibm.__dict__
{'name': 'IBM', 'shares': 50, 'price': 91.23}
>>> hasattr(ibm, 'date')
False

正如我们所见,ibm 实例并没有 date 属性。这展示了 Python 对象的三个重要特性:

  1. 每个实例都有自己独特的属性集。
  2. 对象创建后可以添加属性。
  3. 为一个实例添加属性不会影响其他实例。

现在,让我们尝试用另一种方式添加属性。我们不使用点号表示法,而是直接操作对象的底层字典。在 Python 中,每个对象都有一个特殊属性 __dict__,它将对象的所有属性存储为键值对。

>>> goog.__dict__['time'] = '9:45am'
>>> goog.time
'9:45am'
>>> goog.__dict__
{'name': 'GOOG', 'shares': 100, 'price': 490.1, 'date': '6/11/2007', 'time': '9:45am'}

通过直接修改 __dict__ 字典,我们为 goog 实例添加了一个新属性 time。当我们访问 goog.time 时,Python 会在 __dict__ 字典中查找 'time' 键,并返回其对应的值。

这些示例表明,Python 对象本质上是带有一些额外特性的字典。Python 对象的灵活性允许进行动态修改,这在编程中非常强大且方便。

理解类和实例之间的关系

现在,我们将探索在 Python 中类和实例是如何关联的,以及方法查找是如何工作的。这是一个重要的概念,因为它能帮助你理解在处理对象时,Python 是如何查找和使用方法及属性的。

首先,让我们检查一下我们的实例属于哪个类。了解实例所属的类至关重要,因为它能告诉我们 Python 会在哪里查找与该实例相关的方法和属性。

>>> goog.__class__
<class '__main__.SimpleStock'>
>>> ibm.__class__
<class '__main__.SimpleStock'>

这两个实例都有一个指向 SimpleStock 类的引用。这个引用就像一个指针,Python 在需要查找方法时会使用它。当你在一个实例上调用方法时,Python 会使用这个引用来找到合适的方法定义。

当你在一个实例上调用方法时,Python 会遵循以下步骤:

  1. 它会在实例的 __dict__ 中查找该属性。实例的 __dict__ 就像一个存储区域,所有特定于该实例的属性都保存在这里。
  2. 如果没有找到,它会检查类的 __dict__。类的 __dict__ 存储了该类所有实例共有的所有属性和方法。
  3. 如果在类中找到该属性,就返回该属性。

让我们来实际看一下。首先,验证 cost 方法不在实例字典中。这一步有助于我们理解 cost 方法不是每个实例特有的,而是在类级别定义的。

>>> 'cost' in goog.__dict__
False
>>> 'cost' in ibm.__dict__
False

那么 cost 方法是从哪里来的呢?让我们检查一下类。通过查看类的 __dict__,我们可以找出 cost 方法的定义位置。

>>> SimpleStock.__dict__['cost']
<function SimpleStock.cost at 0x7f...>

该方法是在类中定义的,而不是在实例中。当你调用 goog.cost() 时,Python 在 goog.__dict__ 中找不到 cost,所以它会在 SimpleStock.__dict__ 中查找并在那里找到它。

实际上,你可以直接从类字典中调用方法,将实例作为第一个参数传递(这个参数会变成 self)。这展示了 Python 在你使用普通的 instance.method() 语法时是如何在内部调用方法的。

>>> SimpleStock.__dict__['cost'](goog)
49010.0
>>> SimpleStock.__dict__['cost'](ibm)
4561.5

这本质上就是你调用 goog.cost() 时 Python 在幕后所做的事情。

现在,让我们添加一个类属性。类属性由所有实例共享。这意味着该类的所有实例都可以访问这个属性,并且它只在类级别存储一次。

>>> SimpleStock.exchange = 'NASDAQ'
>>> goog.exchange
'NASDAQ'
>>> ibm.exchange
'NASDAQ'

两个实例都可以访问 exchange 属性,但它并不存储在它们各自的字典中。让我们通过检查实例和类的字典来验证这一点。

>>> 'exchange' in goog.__dict__
False
>>> 'exchange' in SimpleStock.__dict__
True
>>> SimpleStock.__dict__['exchange']
'NASDAQ'

这表明:

  1. 类属性由所有实例共享。所有实例都可以访问同一个类属性,而无需拥有自己的副本。
  2. Python 首先检查实例字典,然后检查类字典。这是 Python 在你尝试在实例上访问属性时查找属性的顺序。
  3. 类充当共享数据和行为(方法)的存储库。类存储了所有实例都可以使用的所有公共属性和方法。

如果我们修改一个具有相同名称的实例属性,它会遮蔽类属性。这意味着当你在该实例上访问该属性时,Python 将使用特定于该实例的值,而不是类级别的值。

>>> ibm.exchange = 'NYSE'
>>> ibm.exchange
'NYSE'
>>> goog.exchange  ## 仍然使用类属性
'NASDAQ'
>>> ibm.__dict__['exchange']
'NYSE'

现在 ibm 有了自己的 exchange 属性,它遮蔽了类属性,而 goog 仍然使用类属性。

总结

在本次实验中,你学习了 Python 对象系统的内部工作原理以及几个关键概念。首先,Python 对象将属性存储在一个可通过 __dict__ 属性访问的字典中,这提供了很大的灵活性。其次,你掌握了属性赋值和查找的工作方式,包括动态添加属性以及属性检查的顺序。

此外,你探索了类和实例之间的关系,其中类包含共享的数据和行为,而实例维护着自己的状态。你还了解了方法调用是如何运作的,类中的方法通过 self 参数作用于实例。理解这些概念能加深你对 Python 面向对象编程(OOP)模型的理解,并且对调试、设计类层次结构以及学习高级特性都很有帮助。