Python map 函数是按引用还是按值传递?
我有个关于Python中map
函数的问题。
根据我的理解,这个函数不会改变它操作的列表,而是会创建一个新的列表并返回。这样理解对吗?
另外,我有一段代码:
def reflect(p,dir):
if(dir == 'X'):
func = lambda (a,b) : (a * -1, b)
else:
func = lambda (a,b) : (a, b * -1)
p = map(func,p)
print 'got', p
points
是一个包含元组的数组,比如:[(1, 1), (-1, 1), (-1, -1), (1, -1)]
如果我这样调用上面的函数:
print points
reflect(points,'X')
print points
那么列表points
是不会改变的。不过在函数内部,打印出来的内容是我想要的。
有没有人能告诉我,关于Python中值传递和引用传递是怎么回事,我该怎么解决这个问题?或者说我是不是在Python中试图过于模仿Haskell……
谢谢!
补充:
假设我不是用p = map(func,p)
,而是这样做:
for i in range(len(p)):
p[i] = func(p[i])
那么列表的值在函数外部被更新,就像是通过引用一样。唉,希望这样说清楚了 :S
3 个回答
我想再提一下这个问题。之前的回答并没有真正解答提问者的问题,忽略了提问者通过循环已经达到了想要的结果。问题的关键在于 map
的行为。这里有一个更直接的例子:
f=(lambda pair: pair[0].append(pair[1]))
a = ([],1)
f(a)
print(a) #prints ([1],1)
a=([],1)
map(f,[a])
print(a) #prints ([0],1)
所以 map
并没有像提问者期待的那样改变对象。我也有同样的困惑。
有没有人能具体说说这里到底发生了什么?我觉得这会是对提问者问题的一个好回答。
注意,如果我们按照 Cat Plus Plus 的回答来赋值 map
的输出,行为就会有所不同:
f=(lambda pair: pair[0].append(pair[1]))
a = ([],1)
x = [a]
x[:] = map(f,x)
print(x) #prints None
print(a) # prints [1]
请注意,在第一个例子中,我们只是调用了 f(a)
,而不是 a=f(a)
。为什么在使用 map
时需要赋值,而在 map
外部工作时却不需要呢?
Python的数据模型基于三个部分:
标识符 - 引用 - 对象
.
- 标识符是代码中写的一个
字符串
。 - 引用是一个变量,简单来说就是“一个可以改变内容的内存块”。引用的值是对象的地址。
- 对象的实现是基于C语言的结构,而C语言是Python的基础。
还有其他词可以用来表示“标识符”:
1) 名字
2) 变量;因为这个词在数学中用来表示代表真实数学变量的符号,而计算机中的变量在概念上和数学变量的功能是一样的(它们的值可以改变)。
我认为在Python中使用这个词是个很糟糕的习惯:它会造成歧义和混淆,因为在计算机科学中“变量”指的是“一个可以改变内容的内存块”。
最好使用“标识符”这个词。
.
标识符和对象在某个命名空间中绑定。命名空间的表现形式像Python的字典,但它们并不是字典。
标识符和对象的绑定是间接的,通过引用实现。
标识符和引用的绑定是直接的,并且是在符号表中实现的。
在计算机科学中,符号表是一种数据结构,语言翻译器(比如编译器或解释器)使用它来将程序源代码中的每个标识符与其声明或出现时的信息关联起来,比如它的类型、作用域级别,有时还包括它的位置。
http://en.wikipedia.org/wiki/Symbol_table
他们说:标识符。没错。
我很少看到对符号表的提及,尽管在我看来,这是理解Python数据模型运作的关键。
在我看来,“
绑定
”这个词并不指代一个精确和独特的机制,而是指代与标识符 - 引用 - 对象三者相关的所有机制的集合。
.
我并不认为自己完全理解Python的数据和执行模型,也不认为上面的观点是最准确和清晰的表达。
然而,这些观点让我对代码执行时发生的事情有了操作上的理解。
如果我有错误的地方,我很乐意被纠正(例如,我很高兴从Michael Foord那里了解到命名空间的本质并不是字典,这只是它们的表现方式)。
.
话虽如此,我不知道在讨论Python中将某个东西作为参数传递时,值和引用是什么,我感觉很多在这个话题上发表意见的人也不比我了解得多。
我认为在这个问题上,没有比Alex Martelli的观点更清晰的了:
“试图将更普遍适用于‘变量是盒子’的语言的术语,应用到‘变量是便签’的语言上,个人认为更可能造成混淆,而不是帮助。”
Alex Martelli
你对Python中引用的工作方式有些误解。在这里,所有的名字都是引用,没有“值”的概念。名字是绑定到对象上的。但是,=
并不会改变名字所指向的对象,而是把名字重新绑定到一个不同的对象上:
x = 42
y = x
# now:
# 'is' is a identity operator — it checks whether two names point to the
# exact same object
print x is y # => True
print x, y # 42 42
y = 69
# now y has been rebound, but that does not change the '42' object, nor rebinds x
print x is y # => False
print x, y # 42 69
要想修改对象本身,它需要是可变的,也就是说,它要有可以改变它的成员或者有一个可修改的字典。就像上面说的,当你重新绑定p
时,它根本不影响points
,只是改变了本地p
这个名字的含义。
如果你想模拟C++那样的引用,你需要把对象放进一个可变的容器里,比如一个列表。
reflect([points], 'X')
# inside reflect:
p[0] = ...
不过在这种情况下,你其实不应该这样做——你应该直接返回新的对象。
points = reflect(points, 'X')
# inside reflect, instead of p = ...
return map(func, p)
嗯,现在想想,你也可以这样做:
p[:] = map(func, p)
但再说一次,通常返回新的对象会更好。