关于 python 里的值传递

2016-12-27 20:12:47 +08:00
 bytest

python 入门很快,可是细枝末节的东西还是很多的,弄不明白。

In [1]: list1 = list(range(5))
In [2]: for i in list1:
   ...:     i = 100
   ...:     
In [3]: list1
Out[3]: [0, 1, 2, 3, 4]

list1 没变化。

In [4]: list2 = []
In [5]: for i in range(5):
   ...:     list2.append(list(range(i)))
   ...:     
In [6]: list2
Out[7]: [[], [0], [0, 1], [0, 1, 2], [0, 1, 2, 3]]
In [8]: for i in list2:
    ...:    i.append('end')
    ...:     
In [9]: list2
Out[9]: [['end'], [0, 'end'], [0, 1, 'end'], [0, 1, 2, 'end'], [0, 1, 2, 3, 'end']]

list2 中的每个列表都变化了,换成 dict 也是变了的。

前面是值传递?后面是引用传递? 抱歉,我知道 c++里这么叫,请教 python 里这是什么特点?

python 的书现在越来越多,入门的都讲的太简单,就跟给小学生看的一样,看完入门书用 python 写代码到处都是问题。 请问大家有没有推荐好些的中高级进阶的书?最好中文,看得快,不要讲 api 或者应用的,要谈语法或者语言特性的?

被 c++虐惯了,换到 python 就只告诉你简单,可是后面的坑得自己一个一个去踩,真心不爽。

2188 次点击
所在节点    Python
13 条回复
WKPlus
2016-12-27 20:34:27 +08:00
python 里面都是引用,所以 a=x 只是把 a 指向另外一个对象并不会修改 a 原来指向的对象的值。然后, python 里面有 mutable 和 immutable 对象的概念,可以了解一下
tonghuashuai
2016-12-27 20:38:47 +08:00
python 中不存在所谓的传值调用,一切传递的都是对象的引用,也可以认为是传址。 python 中,对象分为可变(mutable)和不可变(immutable)两种类型,元组( tuple)、数值型( number)、字符串(string)均为不可变对象,而字典型(dictionary)和列表型(list)的对象是可变对象。

a = 1 #将名字 a 与内存中值为 1 的内存绑定在一起
a = 2 #将名字 a 与内存中值为 2 的内存绑定在一起,而不是修改原来 a 绑定的内存中的值。
Kilerd
2016-12-27 20:45:43 +08:00
in python ,everything is a object.
Kilerd
2016-12-27 20:46:02 +08:00
anything
zmj1316
2016-12-27 20:53:51 +08:00
C++ 里面的 foreach (Range-based for loop) 也是类似的吧
crab
2016-12-27 20:54:35 +08:00
第一次 1 个列表
第二次是 list(range(i)) ,每一次里面又有一次列表,列表从 0 到 i 。
bytest
2016-12-27 20:56:22 +08:00
@crab 我知道……
bytest
2016-12-27 21:01:11 +08:00
@WKPlus
@tonghuashuai
谢谢二位。我知道可变和不可变对象的概念。

自己想明白了,一直以为是 for 处理不同类型有区别,其实这里的关键在于“=”, append 方法修改了引用的对象,即列表中的列表,而“=”是重新绑定引用对象了。我试了第二个循环里如果写成 i = ['end'],原二维列表也不变了。
bytest
2016-12-27 21:06:23 +08:00
@zmj1316 你是说 STL algorithm 库里的 for_each ,还是 c++11 的 range for loop ?这是两个概念。这里问题的关键在于 python 里的“=”赋值的问题。
zmj1316
2016-12-27 21:21:20 +08:00
@bytest 我括号里说明了,并且我没看出来你这个两个样例中 python 的 = 有什么区别,因为你第二个里面没用到 = ,

如果说你第二个里面写的是
In [8]: for i in list2:
...: i = ['end']

倒是可以有比较
LPeJuN6lLsS9
2016-12-27 22:54:06 +08:00
维基有详细解释: https://en.wikipedia.org/wiki/Evaluation_strategy#Call_by_sharing
这翻来覆去讨论的问题,知乎是有,不知道本站有没有

效果和 C++的引用传递类似,不过不能用来把函数调用者作用域内的变量改掉( out parameter 可以返回元祖代替),所以不叫引用传递
CRVV
2016-12-27 23:16:01 +08:00
从行为来说, immutable 的类型和值类型一样, mutable 的类型和指针一样
正经的解释当然是名字绑定什么的那些
等价的 C++ 代码大概是:
```
#include <iostream>
#include <vector>

template<typename T> void print(T& arg) {
std::cout << arg << ", ";
}
template<typename T> void print(std::vector<T>* list) {
for (auto& i : *list) {
print(i);
}
std::cout << std::endl;
}

std::vector<int>* range(int length) {
auto result = new std::vector<int>();
for (int i = 0; i < length; ++i) {
result->push_back(i);
}
return result;
}
int main() {
auto list1 = range(5);
print(list1);
for (auto i : *list1) {
i = 100;
}
print(list1);

auto list2 = new std::vector<std::vector<int>*>();
for (int i = 0; i < 5; ++i) {
list2->push_back(range(i));
}
print(list2);
for (auto i : *list2) {
i->push_back(-1);
}
print(list2);
for (auto i : *list2) {
i = range(0);
}
print(list2);
return 0;
}
```
mnzlichunyu
2016-12-28 12:13:04 +08:00
一般这种奇怪的问题,大多数都能从 bytecode 上找到原因。楼主给的第一种情况的字节码是这样的:
>>> dis.dis(list_change)
2 0 LOAD_GLOBAL 0 (list)
3 LOAD_GLOBAL 1 (range)
6 LOAD_CONST 1 (5)
9 CALL_FUNCTION 1
12 CALL_FUNCTION 1
15 STORE_FAST 0 (list1)

3 18 SETUP_LOOP 20 (to 41)
21 LOAD_FAST 0 (list1)
24 GET_ITER
>> 25 FOR_ITER 12 (to 40)
28 STORE_FAST 1 (each)

4 31 LOAD_CONST 2 (1)
34 STORE_FAST 1 (each)
37 JUMP_ABSOLUTE 25
>> 40 POP_BLOCK
>> 41 LOAD_CONST 0 (None)
44 RETURN_VALUE

您可以注意 index 为 28,34 的 STORE_FAST ,根据字节码来看,你给的第一种情况和下面的代码是等效的:
list1 = list(range(5))
for index in range(len(list1)):
i = list1[index]
i = 100

所以, list1 的内容是不会发生改变的。

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/330575

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX