简介
在这个实验中,你将学习 Python 对象在内部是如何表示的,并理解属性赋值和查找的机制。这些概念是掌握 Python 如何管理对象内的数据和行为的基础。
此外,你将探索类和实例之间的关系,并研究类定义在面向对象编程中的作用。这些知识将加深你对 Python 面向对象特性的理解。
This tutorial is from open-source community. Access the source code
💡 本教程由 AI 辅助翻译自英文原版。如需查看原文,您可以 切换至英文原版
在这个实验中,你将学习 Python 对象在内部是如何表示的,并理解属性赋值和查找的机制。这些概念是掌握 Python 如何管理对象内的数据和行为的基础。
此外,你将探索类和实例之间的关系,并研究类定义在面向对象编程中的作用。这些知识将加深你对 Python 面向对象特性的理解。
在这一步中,我们将创建一个简单的类来表示股票。理解如何创建类是 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
是一个类,那么 goog
和 ibm
就是该类的实例。
要查看这些实例的内部字典,你可以使用 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
实例中,属性 name
、shares
和 price
及其对应的值都存储在字典中。这就是 Python 在幕后实现对象属性的方式。每个对象都有一个私有字典来保存其所有属性。
理解 __dict__
属性揭示的对象内部实现细节很重要:
goog
实例中,键 'name'
对应于对象的 name
属性。'GOOG'
是 goog
实例的 name
属性的值。__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 对象的三个重要特性:
现在,让我们尝试用另一种方式添加属性。我们不使用点号表示法,而是直接操作对象的底层字典。在 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 会遵循以下步骤:
__dict__
中查找该属性。实例的 __dict__
就像一个存储区域,所有特定于该实例的属性都保存在这里。__dict__
。类的 __dict__
存储了该类所有实例共有的所有属性和方法。让我们来实际看一下。首先,验证 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'
这表明:
如果我们修改一个具有相同名称的实例属性,它会遮蔽类属性。这意味着当你在该实例上访问该属性时,Python 将使用特定于该实例的值,而不是类级别的值。
>>> ibm.exchange = 'NYSE'
>>> ibm.exchange
'NYSE'
>>> goog.exchange ## 仍然使用类属性
'NASDAQ'
>>> ibm.__dict__['exchange']
'NYSE'
现在 ibm
有了自己的 exchange
属性,它遮蔽了类属性,而 goog
仍然使用类属性。
在本次实验中,你学习了 Python 对象系统的内部工作原理以及几个关键概念。首先,Python 对象将属性存储在一个可通过 __dict__
属性访问的字典中,这提供了很大的灵活性。其次,你掌握了属性赋值和查找的工作方式,包括动态添加属性以及属性检查的顺序。
此外,你探索了类和实例之间的关系,其中类包含共享的数据和行为,而实例维护着自己的状态。你还了解了方法调用是如何运作的,类中的方法通过 self
参数作用于实例。理解这些概念能加深你对 Python 面向对象编程(OOP)模型的理解,并且对调试、设计类层次结构以及学习高级特性都很有帮助。