Python将什么视为引用类型?

31

我曾经认为Python中的序列类型是值类型。事实证明它们是引用类型(这意味着当将变量赋给新变量时,变量的值不会被复制,而是被引用)。因此现在我想知道,在Python中有哪些值类型?也就是说,有哪些类型的变量可以被赋给新变量而不必担心变量被引用?

3个回答

48
Python中所有的值都是引用。你需要担心的是类型是否是可变的。基本的数值和字符串类型,以及tuplefrozenset都是不可变的;绑定到这些类型对象的名称只能重新绑定,而不能改变。
>>> t = 1, 2, 3
>>> t[1] = 42
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'tuple' object does not support item assignment

12
需要说明的是,虽然元组是不可变的,但其中包含的任何可变对象都可以被修改: t = 1, [2], 3 ; t[1].append(42) - Ned Deily
为了完整起见,您能否添加如何复制/克隆一个值的步骤? - Hubro
3
这要看变量的类型。对于列表,你可以使用list()函数或者对整个列表进行切片(L[:])来复制。对于集合,使用set()函数。对于其他类型,请参考相关文档。需要注意的是,有一些数据类型实际上是无法被克隆的,因为它们代表了外部资源(例如socket)。 - Ignacio Vazquez-Abrams
我觉得这很好而且简洁。谢谢你的回答。 - Smart Humanism

22

从使用强类型的 Swift 语言开发 iOS 应用的经验来看,Python 参考资料有些令人困惑,因此我决定做一下比较。以下是总结:

  • 当将一个变量分配给 Python 时,例如 a = 10,实际上是在指向/引用存储在内存中的对象,即 10。因此,如果该对象发生更改,则变量 a 的值也会更改,但更改 a 不会更改对象 10,这与 Swift 的原始值类型(如 Int)类似。

为了让这一点更加清晰,这里有一个例子:


 # "a" points to an object in this case 10
a = 10

# "b" points to the same object which a points but does not point to a variable a.
b = a 

# Now if we change "a" to point to another object in memory say 20. 
a = 20

# "b" still points to the old object 10 in other words
# "b == 10" but "a == 20", This is because "b" was never pointing to the variable "a" 
# even though we assigned it as "b = a" instead it was pointing to the object 10
#  which is # the same as writing b = 10. 

让我们来检查一个更复杂的数据结构 List

list1 = [10,20,30,40]
list2 = list1 #[10,20,30,40]

list1 = [3,4] 

# list1 ==> [3,4]
# list2 ==> [10,20,30,40]


再次让它表现与Swift和其他类似的语言相同。这里出现了巨大的不同,让我们试着在某个索引处更改值(这变得更加棘手)

list1 = [10,20,30,40]
list2 = list1 #[10,20,30,40]

# change value of list 1 at a certain index say index 0
list1[0] = 500

# If you check again the values of list1 and list2 you will be surprised. 
#list1 ==> [500,20,30,40]
#list2 ==> [500,20,30,40]

它们都会改变,因为它们都指向同一个对象,所以更改对象会更改所有 list1list2。这与其他语言(如Swift)非常令人困惑。在Swift中,List/Array是值类型,这意味着它们不是引用类型,而是被复制传递的,但在Python中则不同,更改特定索引处的值会导致所有引用该对象的属性的值发生更改,就像上面的示例一样。对于从Swift或其他类似语言转来的人来说,这非常重要。

那么我们如何在 python 中复制?

  • 如果您想在python中复制列表,则必须明确地执行以下操作,如下面的示例所示:
list1 = [10,20,30,40]
list2 = list(list1)

# list1 ==> [10,20,30,40]
# list2 ==> [10,20,30,40]

这样做可以避免 list1 发生变化时产生不必要的影响,list2 将保持不变。

举个例子:

list1[0] = 500
#list1 ==> [500,20,30,40] # Changed
#list2 ==> [10,20,30,40] # Unchanged

1
很高兴我能帮到你 :) - Mussa Charles
这真的很有帮助。我来自Swift,当Python的赋值运算符在列表中表现不如预期时感到惊讶。顺便说一下 - 我相信你已经知道了 - 似乎列表有一个.copy()方法,它也会产生一个不附加到原始列表的副本。 - Gallaugher

8
上面的答案是正确的,但我反对“引用”的语义。
C语言类似的语言将变量视为固定的桶,其中放置值。当你调用一个函数时,会创建一组新的桶,并将值复制到其中。有时,一个桶是按引用传递的,并实际上成为调用者桶的别名。
另一方面,Python将变量视为值(对象)的纯标签(名称)。当你调用一个函数时,会创建一组新的标签并贴在这些相同的对象上。
在Python的背景下提到“引用”没有意义,因为在其他任何语言中,“引用”都暗示着与“值”的替代品。Python没有这样的二元性;它只是传递和分配对象。没有被引用的东西。
也许有点挑剔,但这个术语对于C++程序员来说造成了无尽的困惑,例如他们听说Python通过引用传递,并不理解如何重新分配调用者的名称。

4
Python采用按值传递方式,但这些值是引用。 - Ignacio Vazquez-Abrams
这很荒谬。传递的值不是对象的引用;它们就是对象。 - Eevee
2
也许这篇文章(http://bit.ly/4Cjmn0)和链接到它的SO问题(http://bit.ly/3fRXW)可以为此提供一些启示。我发现文章中使用的术语有助于使事情变得更加清晰。此外,虽然我理解你的观点关于引用,但我不同意。在一般意义上,标签或名称就是一个引用。当我使用你的名字时,我正在“引用”你。 - cledoux
我完全同意你的观点,有时在SO上也会因此而争论不休;-) 如果你知道什么是C引用,那么你就知道Python不能传递这些引用,因为那样会使所有对象都变成可变的。我称Python的方式为“通过别名调用”,只是为了避免像这样的问题。另一方面,认为Python通过引用传递对象也可以:该引用由字符串(虚拟机上对象的名称)给出,而不是由数字(实际硬件上的内存地址)给出。这两种观点都有道理,了解它们比单独了解其中任何一种更有帮助 :-) - Jochen Ritzel
我不同意你们认为Python变量就像直接通过它们的名称引用一样。我不确定我的想法是否完全准确,但我的理解是,在Python中,变量名保存对目标值的引用的引用,然后引用的引用值引用目标值,即对象。这就是为什么Python如此缓慢和耗能的原因。这个想法来自于Python是一个脚本语言,是编译器+编译语言的组合。因此,在这种方式下,又增加了一次引用。 - Smart Humanism

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接