例子:
def exampol(x, y):
for i in x:
x[i] = x[i]*x[i]
for j in range(0, len(y)):
y[j] = y[j]*y[j]
return x, y
x = [0,1,2,3,4]
y = [0,10,20,30,40,50,60]
exampol(x, y)
print(x)
print(y)
调用函数exampol()
后输出为:
x = [0, 1, 4, 9, 16]
y = [0, 100, 400, 900, 1600, 2500, 3600]
我不明白为什么会这样。据我所知,函数中的变量在范围内是局部的,传递给函数的参数不应更改其原始值。
为什么将原始变量作为参数传递给修改的函数?
它们是两个不同的概念,一个是名称或标识符(我们称之为“变量”),另一个是与之关联的对象。您的列表未在函数内部定义,您将其作为参数传递。
在 Python中,变量实际上只是一个名称,一个标识符,它与内存中对象的引用相关联并用于访问它。同一个对象可以有多个与之关联的名称:
如果我们这样做:
a
b
它们都是与内存中同一个对象相关联的名称,我们可以list
通过a
重新分配使它们与另一个对象相关联:我们可以使用
a
或与对象关联的任何其他名称来访问它、它的方法和属性:在函数内部定义的变量仅在您评论时存在于该函数内部,并且对函数内部全局变量的任何重新分配尝试都会导致创建一个同名的局部变量,而使全局变量保持不变(只要我们不使用
global
或nonlocal
)。我们可以说函数内部的全局标识符默认是只读的,这是因为解释器首先在函数本地的命名空间中查找变量,如果没有找到它会在全局变量中查找。现在,任何更改函数内附加全局变量的对象的尝试都会自动创建同名的局部变量。当我说“更改与之关联的对象”时,我并不是指修改对象本身的属性,我的意思是使变量指向另一个对象,即赋值:
Python 中的函数/方法参数通过赋值、“对象引用”传递(您不传递对象的值或副本,您只需传递对内存中对象的引用)。
除了通过对象引用传递之外,重要的是要考虑对象可变性的概念(*参见答案末尾的列表)。列表是可变对象,这意味着您在函数内部修改的列表与您作为参数传递给它的列表是相同的对象:
如果对象是不可变的,例如整数、浮点数或字符串,则不会发生这种情况,因为通过变量“修改”不可变对象实际上总是意味着创建新对象并将新引用分配给变量。因此,当我们在函数内部执行类似的操作时
x += 1
,作为x
一个作为参数传递的整数,该名称x
将与内存中的一个新对象相关联,因为序列是将 x 加一,将其存储在一个新对象整数中并关联 (分配)它的名称x
:离开:
由于列表是可变对象,因此在函数内修改其值不会创建新对象,而是直接修改:
离开:
如果您不希望发生这种情况,则必须将列表的副本传递给函数或在函数本身内创建副本:
在这种情况下,通过切片创建副本,您也可以使用
copy.copy()
. 如果您的列表仅包含整数(不可变),这就足够了。如果您的列表具有可变元素,如列表或字典,则上述内容只是创建列表的副本,但其元素与原始列表(相同的引用)保持相同,因此您必须使用copy.deepcopy()
它以使其包含的元素也被复制。Python 对象根据其可变性:
为了完成答案,考虑到@fedorqui 的评论,我根据它们的可变性在标准 Python 中对对象进行了分类:
可变:
bytearray
dict
list
set
不可变:
bool
bytes
complex
decimal
int
float
frozenset
str
/unicode
tuple
range
除了上述之外,在标准库中,我们有一个模块
collections
,它提供了预构建容器的替代品,通常是包装器或基于以前的容器(因此具有与这些相同的不变性属性)。