Python参数传递机制详解,请各位老铁指教

很多文章总结 python 传参,可变对象传引用不可变对象传值,这个说法不够准确。
文章在这里: https://juejin.im/post/5adb33b16fb9a07acb3c71a6
Python参数传递机制详解,请各位老铁指教

16 回复

内容太简单。
你指出的数字例外,不过只是 Python 对小整数的内存优化而已。

PS:大神不要喷我。


Python的参数传递机制其实挺简单的,核心就一句话:参数传递是按对象引用传递的

别被“值传递”或“引用传递”这些术语绕晕了。在Python里,变量就是个标签,贴在一个对象上。当你调用函数时,实际上是把这些标签(引用)复制了一份,然后贴给函数内部的形参。

看个例子就明白了:

def modify_list(lst):
    lst.append(4)  # 这会修改原列表
    lst = [7, 8, 9]  # 这只是让形参lst贴到了新列表上,不影响实参

my_list = [1, 2, 3]
modify_list(my_list)
print(my_list)  # 输出:[1, 2, 3, 4]

关键点:

  1. 如果形参指向的是可变对象(如列表、字典),通过方法修改其内容会影响实参。
  2. 如果给形参重新赋值(用=),只是让形参贴到了新对象上,跟实参没关系了。
  3. 对于不可变对象(如数字、字符串、元组),你没法修改它,所以看起来像是“值传递”。

简单说就是:传引用,但赋值操作会切断引用关系。

“ Python 会将这个变量指向的对象加 2 后,生成一个新的对象,然后再让 i 指向这个新的对象”

像一楼说的,上面这个说法应该是不对的,并不是生成一个新对象,我猜是和 Java 类似的对小型整数有一个优化,提前定义了 128 以下的小整数

fluent python 有很好的讲解

额,python 会对小于 256 的整数做池化,你也可以吧任何你想池化的东西池化。

解决这个问题,推荐再参数传递可变对象时,默认值设置为 None,在函数内部对 None 进行判断后再赋予默认值。
def test(b=None):
b = b or []
b += [1]
print(b)

test() # [1]
test() # [1]
test() # [1]



这个做法也没有问题, 但是更好的办法是:

不要修改参数!!!


用的时候复制一份


def test(b=[]):
var = list(b)
var += [1]
print(var)

####################### 上面的是理论部分################################


####################### 实践部分 #####################################

def test(b=None):
b = b or []
b += [1]
print(b)


这种函数真的不会出现在实际代码中, 因为这个函数一点用的没有

函数大概分三种:

1. 无副作用的查询函数: getXXX(), queryXXX()

这种函数有个特点 : 他们都有返回值.

2. 有副作用的修改函数: list.sort()

这种函数有个特点: 他们会改变调用者 /参数的状态, 但是没有返回这


3. 混血: 既有返回值又有副作用:

比如你有一个函数: getUser(id), 会返回一个 User, 但是在调用的时候它把 User 的 queryCount 属性改变了


def test(b=None):
b = b or []
b += [1]
print(b)


这个函数首先没有返回值, 其次所有的状态都发生在函数作用域之内, 你调用完之后所有的状态都被销毁, 所以也没有副作用.

所有在生产环境中如果看到这种函数,请删了吧, 真的除了增加代码量一点用都没有.

python 确实是会把-5 到 256 的整形缓存 并且对大数也是有缓存的 我在这段里讲的是 python 对于不可变类型的处理 所以隐去了缓存的问题

effective python 也有很好的讲解

感谢指正 更好的办法确实是 copy 一份

然而实际上 in python, everything is an object. 所以都是传引用。

如果用 rust 的思想来讲的话,某些内容满足了 Copy Trait, 所以内部修改不影响外部,最直观的表现就是传参前后的变量没有直接关系。

自增操作符对不可变对象其实是生成一个新的 object 然后修改引用到这个新的 object。
不可变对象和可变对象的自增操作符的行为是完全不同的。

所以我觉得下面这句话不太准确
> 修改传进的可变参数时,会对外部对象产生影响,修改不可变参数时则不会影响。

比如这段:
a = [1, 2, 3]
print(id(a)) # 1437494204232
def mutable(a):
----print(id(a)) # 1437494204232
----a = [2, 3, 4]
----print(id(a)) # 1437494207528

mutable(a)
print(a) # [1, 2, 3]
print(id(a)) # 1437494204232

我理解你第一句话的意思 但是不明白下面那句哪里不太准确 可不可以再详细说一下?

你看我给的例子 在函数里修改传进来的可变对象参数 没对外部对象产生影响

嗯 看到了 不过这个不是修改了 而是重新给 a 赋值 修改应该是 a+=[1]或者 a.append(1)
这块字面意思上看确实有点不太准确 谢谢指正

所以就回到我第一个回复的第一句话了,对于不可变对象,自增操作符实际上是一个赋值的操作。
因此在讨论 Python 的传参的时候 不应该按可变对象不可变对象来分类 而应该而操作符 /函数调用的类型来分类。

回到顶部