理解Python的参数传递、变量赋值及可变对象和不可变对象
熟悉C或Java的的童鞋,在使用Python时,常常会对函数调用时的参数传递方式感到困惑。要想弄明白这一点,需要对Python中“一切皆对象”的数据模型和Python怎样处理变量赋值有所了解。简单来讲,Python的参数传递,既非按值传递(call-by-value),也非按引用传递(call-by-reference),而是按对象引用传递(call-by-object-reference)或者说按赋值传递(call-by-assignment)。下面来梳理一下这些概念。
Python中的变量赋值
在Python中,“一切皆对象”。我们常说的“变量”,在Python中可以理解为“变量名”。而在Python中进行变量赋值,可以理解为将“变量名”与对象进行绑定。
- “变量名”并不是对象本身,只是通过赋值与对象进行了绑定,赋值完成后,就可以通过“变量名”来引用对象。
- 每个“变量名”到对象的绑定有其作用域。
初试Python中的按对象引用传递
前面已经提到,在Python中调用函数时,是传递的对象引用。我们通过下面两个例子来具体解释

第一个例子中,将字符串赋值给string变量,然后向foo函数传递了string参数,在函数中对传递进来的string重新赋值。从打印的结果可以看出,函数内对string重新赋值,不影响函数外的string的值。这个看起来就像是其他语言中的按值传递参数。
第二个例子中,将列表赋值给li变量,然后向bar函数传递了li参数,在函数中对传递进来的li列表追加了元素。从打印结果可以看出,函数内对li列表追加元素后,函数外的li列表也发生了同样的变化。这个看起来就像是其他语言中的按引用传递参数。
可变对象和不可变对象
这看起来似乎有些奇怪,其实这反映了Python中的两个重要概念,即可变对象(mutable object)和不可变对象(immutable object)。可以参考( https://docs.python.org/3/faq/programming.html?highlight=mutable#why-did-changing-list-y-also-change-list-x )。正如名称所表示的,可变对象的内容是可以改变的,不可变对象的内容是不可改变的。在Python中, list, dict, set 等类型的对象是可变对象,而 str, int, tuple等类型的对象是不可变对象。
- 对于可变对象,可以通过一些特定操作,“就地”改变其内容,内容改变后,所有引用该可变对象的变量,都会看到此变化
- 对于不可变对象,任何操作都不能“就地”改变其内容,而只能将内容进行变换生成新的内容,返回新的对象
重新理解Python中的按对象引用传递
理解可变对象和不可变对象的区别后,再结合上面变量赋值中提到的两个规则
- “变量名”并不是对象本身,只是通过赋值与对象进行了绑定,赋值完成后,就可以通过“变量名”来引用对象。
- 每个“变量名”到对象的绑定有其作用域。
就可以更好地理解上面函数调用时发生的事情了:
理解第一个例子
- 向函数foo传递的是不可变的字符串对象"Outside",此对象与函数外的string变量名绑定;
- 函数内的string局部变量名,初始时也与函数外的string变量名一样绑定了同一个字符串对象"Outside";
- 在函数内通过string="Inside"对局部变量sting进行了赋值操作,即将一个新的字符串对象"Inside"与函数内的string局部变量名绑定;
- 函数运行结束后,不会影响函数外的string变量名所绑定的内容。
理解第二个例子
- 向函数bar传递的是可变的列表对象[0],此对象与函数外的li变量名绑定;
- 函数内的li局部变量名,初始时也与函数外的li变量名一样绑定了同一个列表对象[0];
- 在函数内通过li.append(1)对局部变量li进行了追加元素操作,此操作与赋值操作不同,它不会将一个新的列表对象重新绑定到内部变量li,而是就地改变了所绑定对象的内容;
- 函数运行结束后,函数外的li变量名所绑定的内容已然发生了改变。
冀公网安备 13010402001886号