值传递还是引用传递
Python中的变量是没有类型的,我们可以把它看做一个(*void)类型的指针,变量是可以指向任何对象的,而对象才是有类型的。
Python中的对象有不可变对象(number,string,tuple等)和可变对象之分(list,dict等)。
值传递(passl-by-value)过程中,被调函数的形式参数作为被调函数的局部变量处理,即在堆栈中开辟了内存空间以存放由主调函数放进来的实参的值,从而成为了实参的一个副本。值传递的特点是被调函数对形式参数的任何操作都是作为局部变量进行,不会影响主调函数的实参变量的值。(被调函数新开辟内存空间存放的是实参的副本值)
引用传递(pass-by-reference)过程中,被调函数的形式参数虽然也作为局部变量在堆栈中开辟了内存空间,但是这时存放的是由主调函数放进来的实参变量的地址。被调函数对形参的任何操作都被处理成间接寻址,即通过堆栈中存放的地址访问主调函数中的实参变量。正因为如此,被调函数对形参做的任何操作都影响了主调函数中的实参变量。(被调函数新开辟内存空间存放的是实参的地址)。
1. 不可变对象作为函数参数,相当于C语言的值传递。
def test(c):
print("test before ")
print(id(c)) #一开始传递进来的是对不可变数据类型a(数字类型)的引用
c += 2 #数字改变后有一个新的指针指向3所在内存空间,但原来指向2的指针a(内存地址空间依旧存在)未改变
print("test after ")
print(id(c)) #故c的内存地址改变
return c
if __name__ == "__main__":
a = 2
print(id(a))
n = test(a) #4
print(a) #2
print(id(a))
2. 可变对象作为函数参数,相当于C语言的引用传递。(因列表是可变数据类型,作为参数传递实则是传递对列表的引用,修改更新列表值后依旧引用不变)
def test(list2):
print("test before ")
print(id(list2)) #221
list2[1] = 30
print("test after ")
print(id(list2)) #221
return list2
if __name__ == "__main__":
list1 = ["loleina", 25, 'female']
print(id(list1)) #221
list3 = test(list1)
print(list1) #["loleina",30, 'female']
print(id(list1)) #221
print(id(list3))
同时这个一般需要结合拷贝来看:传送门
Python传入参数的方法有:位置参数、默认参数、可变参数(带*)、关键字参数、和命名关键字参数、以及各种参数调用的组合
位置参数:
位置参数是最简单的传入参数的方式,在其它的语言中也常常被使用
def func(a, b):
print(a+b)
func(1, 2) #3
默认参数
默认参数就是在调用函数的时候使用一些包含默认值的参数
定义默认参数要牢记一点:默认参数必须指向不变对象!
<span style="color:#000000">
</span>
def power(x, n=2):
s = 1
while(n > 0):
n -= 1
s *= n
reutrn s
power(3) #9
power(2, 3) #8
可变参数
可变参数就是允许在调用参数的时候传入多个(≥0个)参数(类似于列表、字典)
#传入一个列表,严格地说这不是可变参数
def calc(l):
sum = 0
for n in l:
sum += n
return sum
>>> calc([1,2,3])
7
#这才是可变参数,虽然在使用上和列表没有区别,但是参数nums接收到的是一个tuple(这些参数在传入时被自动组组装为一个元祖)
def calc(*nums):
sum = 0
for n in nums:
sum += n
return sum
>>> calc(1,2,3)
7
>>> my_ls = [1,2,3]
>>> calc(*my_ls)
7
关键字参数
可变参数允许传入0个~多个参数,而关键字参数允许在调用时以字典形式传入0个或多个参数(注意区别,一个是字典一个是列表);在传递参数时用等号(=)连接键和值
#用两个星号表示关键字参数
def person_info(name, age, **kw):
print("name", name, "age", age, "other", kw)
>>> person_info("Xiaoming", 12)
name Xiaoming age 12 other{}
>>> person_info("Dahuang", 35, city = "Beijing")
name Dahuang age 35 other {'city':'Beijing'}
命名关键字参数
命名关键字参数在关键字参数的基础上限制传入的的关键字的变量名
和普通关键字参数不同,命名关键字参数需要一个用来区分的分隔符*
,它后面的参数被认为是命名关键字参数
#这里星号分割符后面的city、job是命名关键字参数
person_info(name, age, *, city, job):
print(name, age, city, job)
>>> person_info("Alex", 17, city = "Beijing", job = "Engineer")
Alex 17 Beijing Engineer #看来这里不再被自动组装为字典
不过也有例外,如果参数中已经有一个可变参数的话,前面讲的星号分割符就不要写了(其实星号是写给Python解释器看的,如果一个星号也没有的话就无法区分命名关键字参数和位置参数了,而如果有一个星号即使来自变长参数就可以区分开来)
#args是变长参数,而city和job是命名关键字参数
person_info(name, age, *args, city, job):
print(name, age, args, city)
>>> person_info("Liqiang", 43, "balabala", city = "Wuhan", job = "Coder")
Liqiang 43 balabala Wuhan Coder
参数组合
总结一下,在Python中一种可以使用5中传递参数的方式(位置参数、默认参数、变长参数、关键字参数、命名关键字参数)
注意,这些参数在书写时要遵循一定的顺序即:位置参数、默认参数、变长参数、关键字参数、命名关键字参数(和本文的行文顺序一致)
def f1(a, b, c=0, *args, **kw):
print("a = ", a, "b = ", b, "args = ", args, "kw = ",kw)
def f2(a, b, c=0, *, d, **kw):
print("a = ", a, "b = ", b, "c = ", c, "d = ", d, "kw = ", kw)
>>> f1(1, 2)
a = 1 b = 2 c = 0 args =() kw = {}
>>> f1(1, 2, c=3)
a = 1 b = 2 c = 3 args = () kw = {}
>>> f1(1, 2, 3, 'a', 'b')
a = 1 b = 2 c = 3 args = ('a', 'b') kw = {}
>>> f1(1, 2, 3, 'a', 'b', x = 99)
a = 1 b = 2 c = 3 args = ('a', 'b') kw = {'x':99}
>>> f2(1, 2, d = 99, ext = None)
a = 1 b =2 c = 0 d = 99 kw = {'ext':None}
写在后面
关于Python参数传递,有以下几点提请注意:
1)参数的传递是通过自动将对象赋值给本地变量名来实现的
函数参数在实际中只是Python赋值的另一个实例而已,因为引用可以是以指针的形式来实现的,所有的参数实际上都是通过指针进行传递的,作为参数被传递的对象从来不自动拷贝
2)在函数内部的参数名的赋值不会影响调用者
在函数运行时,在函数头部的参数名时一个新的、本地的变量名,这个变量名是在函数的本地作用域内的,函数参数名和调用者作用域中的变量是没有区别的
3)改变函数的可变对象参数的值也许会对调用者有影响
换句话说,因为参数是简单地赋值给传入的对象,函数就能够就地改变传入的可变对象,因此其结果会影响调用者;可变参数对函数来说可以做输入和输出的
Python的通过赋值进行传递的机制与C++的引用参数选项不完全相同,但是实际中,它与C语言的参数传递模型相当类似:
1)不可变参数“通过值”进行传递
像整数和字符串这样的对象是不可变对象,它们通过对象引用而不是拷贝进行传递的,但是因为无论如何都不可能在原处改变不可变对象,实际的效果就很像创建了一份拷贝
2)可变对象是通过“指针”进行传递的
列表和字典这样的对象也是通过对象引用进行传递的,这一点与C语言使用指针传递数组很相似,可变对象能够在函数内部进行原处的改变,这一点和C数组很像