据我所知,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]
。
我理解错了什么?
据我所知,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]
。
我理解错了什么?
i
应该怎么做到3
Answers:
我们称它们为参考。他们像这样工作
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
40
,您都在引用内存中的同一对象。要查看此类型,请输入a,b = 256并测试a is b
。现在使用a,b = 257进行尝试。请参阅: stackoverflow.com/a/1136852/3973834 和 codementor.io/python/tutorial/...
变量不是指针。当您分配一个变量时,您会将名称绑定到一个对象。从那时起,您可以使用名称来引用对象,直到该名称反弹为止。
在第一个示例中,名称i
绑定到值5
。将不同的值绑定到名称j
不会产生任何影响i
,因此,当您以后打印时,该值i
的值仍然是5
。
在第二个示例中,将i
和都绑定j
到同一列表对象。修改列表的内容时,无论使用什么名称引用列表,都可以看到更改。
请注意,如果您说“两个列表都已更改”,那将是不正确的。只有一个列表,但是有两个引用它的名称(i
和j
)。
相关文件
从文档:
名称是指对象。名称是通过名称绑定操作引入的。程序文本中名称的每次出现均指该名称的绑定,该名称在包含用途的最内层功能块中建立。
当你做
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列表是可变对象,因此当您从一个引用更改列表,然后从另一个引用查看列表时,您会看到相同的结果,因为它是相同的列表。
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]
了我们的名字都指向的对象的元素。
现有复合对象的值可以更改。
通过名称更改对象的值时,名称不会更改。双方i
并j
仍指向同一个对象,它的值我们可以改变。
+- name i -+ -\
\
--> + <values> -+
/ | elements | --> [1, 2, 3]
+- name j -+ -/ +-----------+
中间对象的行为类似于指针,因为我们可以直接更改它指向的对象并从多个名称中引用它。
i
和j
分配。您从开始i = 5
,j = 3
然后在其余文章中将它们反转。话虽如此,这是imo唯一能在OP中解决这个问题并真正说明幕后情况的答案。
它们不是完全的指针,它们是对对象的引用。对象可以是可变的或不可变的。修改后,将复制不可变对象。可变对象就地更改。整数是不可变的对象,您可以通过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.
“ =”符号左侧的变量曾经被赋予“ =”右侧的值
i = 5
j = i
-j有5
j = 3
-j有3个(覆盖5的值),但是关于i没有任何改变
print(i)
-所以打印5
分配不会修改对象;它所做的就是改变变量指向的位置。更改一个变量点的位置不会更改另一变量点的位置。
您可能会想到列表和字典是可变类型这一事实。有一些运算符可以就地修改实际对象,如果您使用其中之一,则将看到指向同一对象的所有变量的变化:
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。
(在实际的实现中,数字可能根本不是引用类型;更可能的是,变量实际上直接包含值的表示形式,而不是指向它。但是,实现细节不会改变涉及不可变类型的语义)
在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 ++中,您将返回一个内存指针(如果您当然使用指针语法),而不是一个内存对象,这使您可以更灵活地更改引用的地址。