python 函数的参数传递

python 函数的参数传递

1. Function Calling Behavior

The previous section describes the difference between the old behavior and the new. However, it is also useful to have a description of the new behavior that stands by itself, without reference to the previous model. So this next section will attempt to provide such a description.

When a function is called, the input arguments are assigned to formal parameters as follows:

  • For each formal parameter, there is a slot which will be used to contain the value of the argument assigned to that parameter.
  • Slots which have had values assigned to them are marked as ‘filled’. Slots which have no value assigned to them yet are considered ‘empty’.
  • Initially, all slots are marked as empty.
  • Positional arguments are assigned first, followed by keyword arguments.
  • For each positional argument:
    • Attempt to bind the argument to the first unfilled parameter slot. If the slot is not a vararg1 slot, then mark the slot as ‘filled’.
    • If the next unfilled slot is a vararg slot, and it does not have a name, then it is an error.
    • Otherwise, if the next unfilled slot is a vararg slot then all remaining non-keyword arguments are placed into the vararg slot.
  • For each keyword argument:
    • If there is a parameter with the same name as the keyword, then the argument value is assigned to that parameter slot. However, if the parameter slot is already filled, then that is an error.
    • Otherwise, if there is a ‘keyword dictionary’ argument, the argument is added to the dictionary using the keyword name as the dictionary key, unless there is already an entry with that key, in which case it is an error.
    • Otherwise, if there is no keyword dictionary, and no matching named parameter, then it is an error.
  • Finally:
    • If the vararg slot is not yet filled, assign an empty tuple as its value.
    • For each remaining empty slot: if there is a default value for that slot, then fill the slot with the default value. If there is no default value, then it is an error.

In accordance with the current Python implementation, any errors encountered will be signaled by raising TypeError. (If you want something different, that’s a subject for a different PEP.)

2. 传值还是传引用,都不是!

函数传参的本质是调用函数被调函数在运行过程中通信的问题。

值传递2过程中,被调函数的形式参数作为被调函数的局部变量处理,即在堆栈中开辟了内存空间存放由主调函数传递进来的实参的值,这些值从而成为了实参的一个副本。值传递的特点是被调函数对形式参数的操作都是作为局部变量进行,不会影响主调函数实际变量的值。

引用传递3过程中,被用调函数的形式参数虽然也作为局部变量在堆栈中开辟了内存空间,但存放的是主调函数放进来的实参变量的地址。被调函数对形参的任何操作都被处理成间接寻址,即通过堆栈中存放的地址访问主调函数中的实参变量。所以,被调函数对形参的操作会影响到主调函数中的实参变量。

上述是对传值和传引用的通俗解释,但python是一种动态语言,在python中讨论传值还是传引用是没有意义的,真正重要的是搞清楚python赋值过程中是如何分配内存地址的

python 中一切皆对象,数字、列表、字典、元祖、函数等都是对象。变量是对象的引用(又称名字或标签),对象的操作都是通过引用来完成的。在python中,“变量”更准确的叫法是“名字”,赋值操作 “=” 就是把一个名字绑定到一个对象上,就像给对象添加了一个标签。变量本身没有类型信息,类型信息存储在对象中。

python 函数中,参数传递本质上是一种赋值操作,而赋值操作是一种名字到变量的绑定过程。

做一个实验,探究一下python中赋值内存是如何分配的:

l1=[1,2,3]
l2=[1,2,3]
d1={'a':1, 'b':2}
d2={'a':1, 'b':2}
n1=1
n2=1
n3=2
s1="abc"
s2="abc"
s3="efg"
t1=(1,2,3)
t2=(1,2,3)
t3=(2,3,4)
print(id(l1), id(l2), id(l1) == id(l2))
print(id(d1), id(d2), id(d1) == id(d2))
print(id(n1), id(n2), id(n1) == id(n2), id(n3))
print(id(s1), id(s2), id(s1) == id(s2), id(s3))
print(id(t1), id(t2), id(t1) == id(t2), id(t3))

在我的电脑上运行输出为:

4571361360 4571361520 False
4571362080 4571362320 False
4535413552 4535413552 True 4535413584
4537714672 4537714672 True 4571116400
4569269904 4571487120 False 4571430336

我们可以看到,新建列表、字典、元组会分配新的地址。新建数字、字符串时,如果内存中已存在则变量名直接指向已有地址。

参考

PEP-3102文档

廖雪峰-Python教程-函数的参数

深入理解python中函数传递参数是值传递还是引用传递

  1. 可变长度参数 

  2. pass-by-value 

  3. pass-by-reference 

updated_at 28-06-2019