Python:对象与可变性

在Python中,一切都是对象。 这主要是Guido Van Rossum(Python编程语言的创建者)的设计原则“一切都一流”的副作用。 一流的一切意味着一切都是其他事物的实例。 更一般而言,这意味着所有其他事物都处于同一“层次”上。 看一下以下内容(一切都在python3解释器中运行):

我们使用了type()方法来查看对象属于什么类。

甚至数据类型都是它们各自类的对象。 因此,如果数据类型只是类的实例,我们可以想象不同的数据类型具有不同的属性。 例如,列表是可变数据类型,而元组和内置数据类型(例如int和float)是不可变的。 请注意以下几点:

我们使用id()方法检查对象的内存地址

在第一帧中,我们创建两个相同的字符串。 由于字符串是不可变的对象(您不能更改字符串),因此Python将两个变量都指向同一个对象,从而节省了内存使用量。 我们可以看到这是真的,因为string1和string2共享相同的内存地址(我们可以使用id()检查对象的内存地址)。 但是,在第二帧中,我们尝试使用“ + ='e'”来更改string1的值。 由于字符串是不可变的,因此Python被迫创建一个新的字符串对象,该对象指向字符串“ cate”。 使用整数观察相同的行为:

尽管整数似乎以相同的方式起作用,但这实际上是出于不同的原因。 为了节省内存使用量,CPython在启动时会预先分配(或绑定)前262个整数。 这意味着,数字-5-256(含)自动绑定到内存中的某些地址。 这就是在上面的示例中a和b引用内存中相同位置的原因。 请注意以下几点:

由于300未预装在内存中,因此a和b指向不同的地址

从技术上讲,CPython创建了名为NSMALLPOSINTS和NSMALLNEGINTS的宏。 这些宏分别引用-5和256。 CPython实际上将对所有这些整数对象的引用存储在数组中。 当我们“创建”该范围内的整数(例如:a = 5)时,实际上是在告诉变量指向存储在该数组中的地址。 数组设置为此特定范围,因为它们是最常用的数字。 一旦您要求一个超出此范围的数字,CPython将被迫开始寻找可以存储数字的新内存位置。

当然,如果我们希望a和b指向同一个对象(即使它们不在宏的范围内),我们也可以将一个变量的内存引用传递给另一个:

现在a和b指向同一个对象

最后一个示例演示了一种称为“别名”的机制。 由于a和b现在指向同一对象,因此可以说该对象是别名的,或者a是b的别名,反之亦然。

由于整数是不可变的对象,因此当我们在第二帧中更改a的值时,我们将强制Python创建一个新对象。 最后两个示例展示了一种思考Python中变量的明智方法。 Python中的变量本质上是我们用来引用对象的名称。 他们就像标签。 在上面的整数示例中这很明显,因为即使我们说“ a = 4”(告诉a指向完全不同的对象),我们仍然使用名称或“标签”来引用新对象。

现在,我们对Python处理不可变对象的方式有所了解。 让我们检查可变对象。

列表是可变对象。 让我们初始化两个列表:

有趣的是,即使它们包含相同的数据,Python也不试图节省它们并使列表a和列表b指向相同的对象。 列表a和列表b是不同的对象。 由于列表是可变的,因此Python知道您可能会在这些列表中添加或删除元素。

现在我们了解了可变和不可变对象的一些重要属性,让我们了解一些可用于测试对象状态的运算符。

在上面的示例中,我们使用“ ==”运算符来测试我们的变量是否指向包含相同数据的对象。 由于我们的两个列表都包含相同的数据,因此计算结果为“ True”。

在上面的示例中,我们使用“ is”运算符来测试变量是否指向同一对象。 由于a和be指向不同的对象,因此其结果为“ False”。

为了获得视觉效果,我们可以通过下图来想象上面的场景。 左侧的a和b分别指向包含字符串“ banana”的对象的视觉效果代表了我们在上面创建的情况。

为了获得右侧的视觉效果,其中a和b指向同一对象,我们需要执行以下操作:

现在,我们将两个列表都设置为指向同一对象。 我们在第二张图中验证了这一点,在该图中我们打印了每个变量指向的地址。 此示例还涉及Python中赋值和引用之间的区别。 我们可以将变量视为对内存中特定位置的名称或引用。 这些存储位置包含对象(值)。 例如,语句a = 5告诉或“绑定”名称“ a”以指向内存中保存值5的某个位置。我们可以通过告诉变量引用内存中的另一个位置来“重新绑定”变量-a = 6。

现在,让我们简要看一下元组。 元组是不可变的数据结构,但是它们可以在其中包含可变对象,例如列表。 创建元组后,您无法将其添加或添加到元组中,但是您可以更改存储在元组中的可变对象。 请注意以下几点:

在此示例中,我们创建一个元组(“ dum”),并在其中包含一个列表。 然后,我们将存储在元组中的列表对象分配给另一个变量“ dee”。 然后,我们验证元组对象和元组内部的列表对象是否位于不同的内存地址。 然后,我们尝试更改存储在元组中的列表。 我们确认列表在“ dee”和“ dum”中均已更改。 元组是不可变对象的独特实例,其中可以包含可变对象。 您可以更改这些可变对象。

Python中最后一个唯一的数据结构是冻结集。 冻结集是集合对象的不可变版本。 虽然可以从常规Python集中添加和删除元素:

不能从冻结集中添加或删除任何元素:

那么为什么这真的很重要呢? 我们为什么要关心Python如何对待可变和不可变的对象?

首先,您应该注意一下,因为如果您想成为一个熟练的程序员,那么您应该始终清楚地知道变量的最新情况。

其次,这很重要,因为Python将对象传递给函数的方式受这些对象的可变性影响。 如果将不可变对象传递给函数,则传递的行为类似于“按值调用”。 请注意以下几点:

在第一帧中,我们定义一个递增并打印整数变量的函数。 我们告诉函数打印对象ID以进行调试。 在第二帧中,我们调用该函数。 打印的第一个id与打印在函数外部的整数对象b的id匹配。 这就告诉我们对象b是通过引用函数increment()传递的。 但是,第二个ID(在增量之后打印)是不同的。 一旦达到增量步骤并意识到必须更改对象的值,Python就会使用旧对象的值创建一个不同的整数对象,并递增该对象。 这也是为什么函数中的对象的值为6,而函数之外的对象的值为5的原因。

我们可以对可变对象执行相同的练习:

我们从第一个id语句中看到,就像上次一样,对象是通过引用传递给函数的。 但是与上次不同,由我们的第二个id方法(在append()之后执行)打印的地址对应于同一对象。 由于列表是可变数据结构,因此Python能够仅将元素添加到现有列表中,而无需创建全新的对象。 我们还可以看到,更新后的列表仍然存在于我们的功能范围之外。

这就是为什么人们说Python既不通过值也不通过引用传递,而是通过“按对象引用调用”传递。

总之,Python中的可变对象包括:列表,字典,集合和字节数组。 不可变的对象包括:int,float,complex,tuple,string,frozen set,bytes。 希望您对Python处理可变和不可变对象的不同方式感到更加自信。

From: https://hackernoon.com/python-objects-and-mutability-397f7de38bb5

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值