据我所知,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]
。
我理解错误是什么?
答案 0 :(得分:78)
我们称他们为参考。他们像这样工作
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
答案 1 :(得分:29)
变量不是指针。分配给变量时,您将绑定该对象的名称。从那时起,您可以使用名称来引用该对象,直到该名称被反弹。
在第一个示例中,名称i
绑定到值5
。将不同的值绑定到名称j
对i
没有任何影响,因此当您稍后打印i
的值时,该值仍为5
。
在第二个示例中,您将i
和j
绑定到相同列表对象。修改列表的内容时,无论使用哪个名称来引用列表,都可以看到更改。
请注意,如果您说“两个列表都已更改”,那将是不正确的。只有一个列表,但它有两个引用它的名称(i
和j
)。
相关文档
答案 2 :(得分:11)
来自docs:
名称指的是对象。名称由名称绑定操作引入。 程序文本中每次出现的名称都是指在包含该用途的最里面的功能块中建立的名称的绑定。
当你这样做时
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]
所以i
和j
都指向同一个列表。然后你的例子改变了列表:
i[0] = 5
Python列表是可变对象,因此当您从一个引用更改列表,并从另一个引用中查看它时,您将看到相同的结果,因为它是相同的列表。
答案 3 :(得分:7)
TLDR:Python 名称的作用类似于具有自动取消引用功能的指针,但不允许显式的指针操作。其他目标表示间接寻址,其行为类似于指针。
CPython实现在幕后使用pointers of type 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 -+ -/ +----------+
结果是changing the target of one name does not affect the other。只会更新特定名称指向的内容。
Python还具有other kinds of name-like elements:属性引用(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
在底层使用PyObject*
指针的C数组。可以再次将其粗略地翻译为带有单独名称和对象的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]
,即我们两个名称都指向的对象的元素。
现有复合对象的值可能会更改。
在通过名称更改对象的值时,名称不会更改。 i
和j
仍然引用同一对象,我们可以更改其值。
+- name i -+ -\
\
--> + <values> -+
/ | elements | --> [1, 2, 3]
+- name j -+ -/ +-----------+
中间对象的行为与指针类似,因为我们可以直接更改它指向的对象并从多个名称中引用它。
答案 4 :(得分:5)
它们不是指针,它们是对象的引用。对象可以是可变的,也可以是不可变的。修改不可变对象时会复制它。可变对象就地改变。整数是一个不可变对象,由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.
答案 5 :(得分:1)
当您设置j=3
标签j
不再适用(点数)到i
时,它会开始指向整数3
。名称i
仍然指的是您最初设置的值5
。
答案 6 :(得分:1)
分配不会修改对象;它所做的就是改变变量指向的位置。改变一个变量指向的位置不会改变另一个指向的位置。
您可能正在考虑数组和字典是可变类型的事实。有些操作符可以就地修改实际对象,如果使用其中一个,您将看到指向同一对象的所有变量的更改:
x = []
y = x
x.append(1)
# x and y both are now [1]
但是赋值仍然只是移动指针:
x = [2]
# x is now [2], y is still [1]
数字是值类型,这意味着实际值是不可变的。如果您执行x=3; x += 2
,则不会将数字3转换为数字5;你只是让x
指向5而不是3. 3仍然没有变化,任何指向它的变量仍然会看到3作为它们的值。
(在实际实现中,数字可能不是引用类型,变量实际上直接包含值的表示而不是指向它,但这种区别不会改变值类型所涉及的语义。)
答案 7 :(得分:1)
“=”符号左侧的任何变量都被指定为“=”右侧的值
i = 5
j = i
--- j有5
j = 3
--- j有3(覆盖5的值)但是没有任何关于我的改变
print(i)
- 所以打印5