函数的参数
形参 & 实参
x和y其实就是变量名,在定义的阶段其实是不占用内容的。但是1和2是变量的值,它是要占用空间的,因此形参其实就是定义了一堆变量名,而实参则是实际的变量的值。其实就是相当于x=1
,y=2
。x和y指向了1和2所在的内存地址(绑定关系)。这种绑定关系只有在调用的时候才会生效,调用结束后失效,这种绑定关系只在函数内部有效:
x = "我是外部的"
def test(x,y):
print(x)
print(y)
test("我是内部的",2)
print(x)
结果:
我是内部的
2
我是外部的
在调用的时候,实参的位置也可以放变量,不一定是放具体的值。当然你传一个列表,传一个字典……都是可以的。你传啥就是啥。实参的值要是不可变类型(数字、元组、字符串),虽然列表这种可变类型你也可以传,但是不可变类型传的就是值,可变类型传的是内存地址,生效范围就不是函数内部了,看下面的例子:
不可变类型:
def test(x):
x = 3
x = 1
test(x)
print(x)
结果:1
可变类型:
def test(x):
x.append(4)
x = [1,2,3]
test(x)
print(x)
结果:
[1,2,3,4]
因为可变类型传的是内存地址,因此改了就是直接去操作对应的内存地址的数据了,因此数据被影响了。因此特别注意,不要在函数中动全局变量,只改内部就够了,也就是传值的时候别传递可变类型。
位置参数 & 默认参数
实参的角度
- 按位置传值
def test(x,y):
print(x)
print(y)
test(1,2)
1对应x,2对应y,从实参的角度,这叫按位置传值。
- 按关键字传值
def test(x,y):
print(x)
print(y)
test(x=1,y=2)
按照关键字进行传值,你可以换位置,随便换都没事,因为这边按照关键字明确的指定了。
- 位置传值+关键字传值混用
def test(x,y):
print(x)
print(y)
test(1,y=2)
这种情况下收到位置传值的限制,也就是说1的位置不能变,你把1和y=2的位置换一下就会报错了,因为y有指定值,但是1却没办法对应x了,报错信息如下:
SyntaxError: positional argument follows keyword argument
不能对一个形参多次赋值,不然会报错。也就是说你不管是用位置传参还是关键字传参,不能进行多次传值,不能重复传值。
形参的角度
- 位置参数:必须传值的参数,如果你定义一个位置参数就必须给他传值。经常发生变化的就定义成位置参数。
def test(x,y):
print(x)
print(y)
不传值就会报错:
TypeError: test() missing 1 required positional argument: 'x'
- 默认参数,不经常发生变化的就定义成默认参数。
def test(x,y="hahaha"):
print(x)
print(y)
test(1)
test(3,4)
结果:
1
hahaha
3
4
在默认参数的时候传值不是必须的,如果没有传值不会报错直接会使用默认的。上面默认的就是y=“hahaha”。默认参数必须放到位置参数的后面,不然会报错。
再来看一个问题:
test1 = "test"
def test(x,y=test1):
print(x)
print(y)
test1 = "hey!!"
test(1)
结果:
1
test
可以发现test1 = “hey!!”并没有赋值进去,因为在def定义阶段,y=test1这个就已经加载到内存中了,也就是说y='test'就已经确定了。后续的赋值便不会再印象函数这里面的y了。
可变参数
- *args(这只是一种约定俗称的写法,你可以用*a,或者*b)
def foo(x,*args):
print(x)
print(args)
foo(1,2,3,4,5,6,7,8)
结果:
1
(2, 3, 4, 5, 6, 7, 8)
*args会把按位置传参多余的部分已元组的形式存放到args里面,而不会报错。一般*args和默认参数不会放到一块去用。因此*args
的作用就是接受无穷无尽的参数,之前我们再写可以接受参数的函数的时候会用def foo(x,y,z……)
,假如我需要接受的位置参数有很多,我们就可以使用*args
来替代,简单来说我们就可以用它来带位置参数。
def foo(*args):
xxxxx
同样的在调用的时候实参也可以使用*的形式,比如:
def foo(x,y,z):
print(x)
print(y)
print(z)
foo(*(1,2,3)) ==等同于== foo(1,2,3)
相当于将这个(1,2,3)的元组给打散了去传值。
- **args
比*args多了个星号,区别就在于一个*
会把多余的元素转换成元组,但是**args
的情况会把多余的元素转换成字典,因为是字典肯定就是键值对,要写成下面的格式:
def foo(x,**args):
print(x)
print(args)
foo(1,a=2,b=3,c=4,d=5,e=6,f=7,g=8)
结果:
1
{'a': 2, 'b': 3, 'c': 4, 'd': 5, 'e': 6, 'f': 7, 'g': 8}
如果调用foo不是一键值对的形式传值的话会报错,会报问题说我只要一个位置参数你为啥给我那么多。
总结一下
- *args就是按照位置参数传值,如果多余的就给*args
- **args是关键字传值,也就是a=1这种的,多余的会被**args接收。
def foo(*args,**kwargs)
这种形式就可以接受各种类型的了各种值了。
但是注意两点(实参的角度)
1--不能重复传值
2--关键字传值要在位置传值的后面
那么*可以用在实参的位置,打散了去看,同样的**也是一样,打散了是可以的,但是注意关键字要对应,比如形参是x,你别给传一个a=xx就行:
def foo(x,y,z):
print(x,y,z)
foo(**{"x":1,"y":2,"z":3})
结果:
1 2 3