Python中,函数参数传递是通过对象的引用进行的,我们可以进行下面的验证。
def use_name(val):
print("name id :%s" % (id(val)))
val = "hanshu1"
print("name id modified :%s" % (id(val)))
def test_ref():
name = "hanshu"
print("name id :%s" % (id(name)))
use_name(name)
test_ref()
执行上面的代码,结果如下:
通过Python内置的id函数,用于获取对象的唯一id,它是对象的内存地址,会在创建时分配给对象。
因此,在use_name函数内和函数外,我们都得到了同样的id,说明函数在传递参数时,传递的是对象的引用。当我们给val重新赋值时,它的id也发生了变化,说明这个值是在内存上重新分配的。
函数传参既然是通过对象的引用进行,那么在函数内对对象进行修改,势必会影响到函数外。
那么哪些情况会影响到函数外,哪些不会呢?
Python的数据类型列表如下:
-
数值型:包括整数(int)、浮点数(float)、复数(complex)等;
-
字符串型:字符串,文本信息;
-
list(列表):用于存储一系列数据,可以是不同类型的数据;
-
set:存储一系列唯一的数据,不支持索引和切片操作;
-
tuple(元组):类似于列表,用来存储一系列数据,但是元组不可变,即不能修改元组中的元素;
-
dict(字典):以键值对的形式存储数据,用来表示映射关系;
-
bool(布尔值):表示真或假,取值为:True和False;
-
自定义数据类型
这些数据类型创建的值分为不可变对象和可变对象。
顾名思义,对于不可变对象,在函数中,只能通过重新赋值来对其进行“修改”(这里实际上是重新创建);可变对象,在函数中,可以通过修改其暴露的数据成员来进行修改。
因此,在下面的代码中,当我们在函数内部修改dt参数时,函数外部也受到了影响。
def modify_dict(dt):
dt["name"] = "item2"
print("modify_dict:{}".format(dt))
def test_dict():
dt = { "name": "item1"}
modify_dict(dt)
print("dt:{}".format(dt))
test_dict()
运行结果如下:
也就是说,如果是可变对象,在函数内部对其进行修改,会影响到函数外部。这里的修改,其实就是指通过对成员变量进行赋值或者调用其成员函数的方式改变对象的成员数据。
那么我们自定义的类型是可变对象还是不可变对象呢?
先看看下面的代码:
class MyItem:
def __init__(self, val):
self._data = val
def modify_my_item(my_item):
my_item._data = 4
print("my_item id:{}".format(id(my_item)))
def test_my_item():
my_item = MyItem(3)
print("my_item._data = {}".format(my_item._data))
print("my_item id:{}".format(id(my_item)))
modify_my_item(my_item)
print("my_item id:{}".format(id(my_item)))
print("my_item._data = {}".format(my_item._data))
输出结果如下:
在函数内部修改了我们的自定义类型的字段,my_item的id一直没变,即对象本身没变,但是_data的值变了。
无赖Python中没有真正的私有成员变量,因此,我们的自定义类型也不可能做成不可变对象。因此,我们虽然在例子代码中,为了证明_data值发生了变化,而在modify_my_item被调用之后,再次去读取了_data的值,但是在实际项目中,我们应该尽量避免这么做。如果确实需要,那么要在传入对象的拷贝而非对象本身(这是比较昂贵的操作)。
代码我已经上传gitee
python_study: 用于学习Python语言和库功能特性
大家可以自己clone一个试试。使用命令运行:
git clone https://gitee.com/hanshu_alan/python_study.git
cd python_study/types_and_parameters
python main.py
cd python_study/param_as_ref
python main.py
代码在持续更新中,有问题欢迎留言交流。