是python变量指针吗?否则他们是什么?


81

据我所知,Python中的变量只是指针。

基于此规则,我可以假定此代码段的结果为:

i = 5
j = i
j = 3 
print(i)

会是3。但是我得到了一个意想不到的结果,那就是5

此外,我的Python书确实涵盖了以下示例:

i = [1,2,3]
j = i
i[0] = 5
print(j)

结果将是[5,2,3]

我理解错了什么?



5
我不知道i应该怎么做到3
Tooniis

@Tooniis j现在指向一个不同的内存块,而不是我所指向的内存块。您可能现在已经了解了,但以防万一有人看到它
Swaroop Joshi

Answers:


88

我们称它们为参考。他们像这样工作

i = 5     # create int(5) instance, bind it to i
j = i     # bind j to the same int as i
j = 3     # create int(3) instance, bind it to j
print i   # i still bound to the int(5), j bound to the int(3)

小整数被拘禁,但这对这个解释并不重要

i = [1,2,3]   # create the list instance, and bind it to i
j = i         # bind j to the same list as i
i[0] = 5      # change the first item of i
print j       # j is still bound to the same list as i

3
约翰,您好,“小学生被拘留”是什么意思?谢谢!
yuqli

6
@yuqli在python中,一切都是对象,包括数字。由于很少使用(-5,256),因此它们被“ interned”或缓存在CPython中。因此,每次您键入时40,您都在引用内存中的同一对象。要查看此类型,请输入a,b = 256并测试a is b。现在使用a,b = 257进行尝试。请参阅: stackoverflow.com/a/1136852/3973834codementor.io/python/tutorial/...
埃文Rosica

3
以我的经验,在Python开发人员中更称呼它们为“名称”。术语“引用”带有不必要的C包,可能会使Python(该语言)对CPython(实现)的偏向过多,而CPython恰好使用引用计数。
wim

33

变量不是指针。当您分配一个变量时,您会将名称绑定到一个对象。从那时起,您可以使用名称来引用对象,直到该名称反弹为止。

在第一个示例中,名称i绑定到值5。将不同的值绑定到名称j不会产生任何影响i,因此,当您以后打印时,该值i的值仍然是5

在第二个示例中,将i和都绑定j同一列表对象。修改列表的内容时,无论使用什么名称引用列表,都可以看到更改。

请注意,如果您说“两个列表都已更改”,那将是不正确的。只有一个列表,但是有两个引用它的名称(ij)。

相关文件


15

Python变量是绑定到对象的名称

文档

名称是指对象。名称是通过名称绑定操作引入的。程序文本中名称的每次出现均指该名称的绑定,该名称在包含用途的最内层功能块中建立。

当你做

i = 5
j = i

这和做的一样:

i = 5
j = 5

j不指向i,并且在分配后j不知道该对象是否i存在。 j仅与i分配时指向的内容绑定。

如果您在同一行上进行分配,则如下所示:

i = j = 5

结果将完全相同。

因此,以后做

i = 3

不会更改j指向的内容-您可以交换它-j = 3不会更改i指向的内容。

您的示例不会删除对列表的引用

因此,当您执行此操作时:

i = [1,2,3]
j = i

这样做是一样的:

i = j = [1,2,3]

因此ij两者都指向同一列表。然后,您的示例更改了列表:

i[0] = 5

Python列表是可变对象,因此当您从一个引用更改列表,然后从另一个引用查看列表时,您会看到相同的结果,因为它是相同的列表。


9

TLDR:Python名称的作用类似于具有自动取消引用功能的指针,但不允许显式的指针操作。其他目标表示间接,其行为类似于指针。


CPython实现在幕后使用类型的指针PyObject*。这样,可以将名称语义转换为指针操作。关键是将名称与实际对象分开。

示例Python代码同时包含名称(i)和对象(5)。

i = 5  # name `i` refers to object `5`
j = i  # ???
j = 3  # name `j` refers to object `3`

可以将其粗略地翻译为具有单独名称和对象的C代码。

int three=3, five=5;  // objects
int *i, *j;           // names
i = &five;   // name `i` refers to position of object `5`
j = i;       // name `j` refers to referent of `i`
j = &three;  // name `j` refers to position of object `3`

重要的是,“指针命名”不存储对象!我们没有定义*i = five,但是i = &five。名称和对象彼此独立存在。

名称仅指向内存中的现有对象。

从名称分配到名称时,不交换任何对象!当我们定义时j = i,它等效于j = &five。既不i也不j被连接到另一个。

+- name i -+ -\
               \
                --> + <five> -+
               /    |        5 |
+- name j -+ -/     +----------+

结果,更改一个名称的目标不会影响另一名称。它仅更新特定名称指向的内容。


Python还具有其他类似名称的元素:属性引用(i.j),订阅(i[j])和切片(i[:j])。与直接引用对象的名称不同,所有三个间接引用对象的元素

示例代码包括名称(i)和预订(i[0])。

i = [1,2,3]  # name `i` refers to object `[1, 2, 3]`
j = i        # name `j` refers to referent of `i`
i[0] = 5     # ???

CPython在内部list使用CPyObject*指针数组。可以再次将其粗略地翻译为具有单独名称和对象的C代码。

typedef struct{
    int *elements[3];
} list;  // length 3 `list` type

int one = 1, two = 2, three = 3, five = 5;
list values = {&one, &two, &three};  // objects
list *i, *j;                         // names
i = &values;             // name `i` refers to object `[1, 2, 3]`
j = i;                   // name `j` refers to referent of `i`
i->elements[0] = &five;  // leading element of `i` refers to object `5`

重要的是我们没有更改任何名称!我们确实更改i->elements[0]了我们的名字都指向的对象的元素。

现有复合对象的值可以更改。

通过名称更改对象的值时,名称不会更改。双方ij仍指向同一个对象,它的值我们可以改变。

+- name i -+ -\
               \
                --> + <values> -+
               /    |  elements | --> [1, 2, 3]
+- name j -+ -/     +-----------+

中间对象的行为类似于指针,因为我们可以直接更改它指向的对象并从多个名称中引用它。


1
我非常喜欢这个答案,但是我认为您在示例中颠倒了ij分配。您从开始i = 5j = 3然后在其余文章中将它们反转。话虽如此,这是imo唯一能在OP中解决这个问题并真正说明幕后情况的答案。
杰里米·拉德克里夫

1
@jeremyradcliff感谢您的注意。现在应该修复。让我知道是否想念更多。
MiyaMiyagi '19

7

它们不是完全的指针,它们是对对象的引用。对象可以是可变的或不可变的。修改后,将复制不可变对象。可变对象就地更改。整数是不可变的对象,您可以通过i和j变量来引用。列表是可变对象。

在你的第一个例子中

i=5
# The label i now references 5
j=i
# The label j now references what i references
j=3
# The label j now references 3
print i
# i still references 5

在第二个示例中:

i=[1,2,3]
# i references a list object (a mutable object)
j=i
# j now references the same object as i (they reference the same mutable object)
i[0]=5
# sets first element of references object to 5
print j
# prints the list object that j references. It's the same one as i.

“修改后,将复制不可变对象。” 这有点自相矛盾。
下午19年

1

设置j=3标签j不再适用(指向)时i,它开始指向整数3。名称i仍指您最初设置的值5



1

分配不会修改对象;它所做的就是改变变量指向的位置。更改一个变量点的位置不会更改另一变量点的位置。

您可能会想到列表和字典是可变类型这一事实。有一些运算符可以就地修改实际对象,如果您使用其中之一,则将看到指向同一对象的所有变量的变化:

x = []
y = x
x.append(1)
# x and y both are now [1]

但是分配仍然只是将指针移动:

x = [2]
# x now points to new list [2]; y still points to old list [1]

与字典和列表不同,数字是不可变的。如果这样做x = 3; x += 2,则不是将数字3转换为数字5;而是将数字3转换为数字5。您只是将变量x指向5。3仍然没有变化,指向它的任何变量仍将其值视为3。

(在实际的实现中,数字可能根本不是引用类型;更可能的是,变量实际上直接包含值的表示形式,而不是指向它。但是,实现细节不会改变涉及不可变类型的语义)


1
这不是值类型的意思。值类型恰好意味着您在上一段中描述的内容(值被传递/复制,而不是对对象的引用),并且内部一样(在CPython和PyPy sans JIT编译器中-每个整数都是一个堆分配的对象)。只是坚持不变,这就是您在那里需要的单词。

-1

在Python中,一切都是对象,包括返回的内存本身。这意味着,在创建新的内存块时(无论您创建了什么内容:int,str,自定义对象等),您都会拥有一个新的内存对象。在您的情况下,这是对3的赋值,它创建一个新的(内存)对象,因此具有一个新的地址。

如果运行以下内容,您将很容易理解我的意思。

i = 5
j = i
print("id of j: {}", id(j))
j = 3
print("id of j: {}", id(j))

IMO,内存方面,这是C和Python之间的关键理解/区别。在C / C ++中,您将返回一个内存指针(如果您当然使用指针语法),而不是一个内存对象,这使您可以更灵活地更改引用的地址。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.