⼀. 函数参数--动态传参
如果我们需要给⼀个函数传参, ⽽参数⼜是不确定的. 或者我给⼀个函数传很多参数, 我的形参就要写很多, 很⿇烦, 怎么办呢. 我们可以考虑使⽤动态参数.
形参的第三种: 动态参数。 动态参数分成两种:
- 动态接收位置参数
⾸先我们先回顾⼀下位置参数, 位置参数, 按照位置进⾏传参。
def hobby(h1,h2,h3):
print(h1,h2,h3)
hobby('看书','下棋','看电影')
可以看到,现在只是输入了三个爱好,但是每个人的爱好肯定是不止三个,或者如果只有一个爱好,那么多出的部分就要给一个空值,这样才不会报错,否则,不能与形参一一 对应。所以,就需要使用动态传参了。
def hobby(*hohhy):
print(hohhy)
hobby('看书', '下棋', '看电影','听相声','打篮球')
结果:
('看书', '下棋', '看电影', '听相声', '打篮球')
在形参的前面添加一个*,这样就表示是动态接收传递过来的参数了。而且这样,可以接收任意个数的参数,想多少就多少,不传也可以。
动态接收参数的时候要注意: 动态参数必须在位置参数后面。
再看下一段代码。
def hobby(*hohhy,h1,h2):
print(hohhy,h1,h2)
h1,h2两个参数,放在hobby后面,这样设置后,再去打印,就会报错:TypeError: hobby() missing 2 required keyword-only arguments: 'h1' and 'h2'。意思是,两个被要求的关键字参 数丢失了,也就是没给传。其实,很好理解,前面的*hobby,接收的是位置参数,所以前面传递的所有位置参数都被这个*hobby接收了,后面的h1,h2,就没有值可接收了。也就 会报出上面的错误,丢失了两个参数。那么怎么处理这个代码呢,看下面的代码:
def hobby(h1,h2,*hohhy):
print(h1,h2,hohhy)
hobby('看书', '下棋', '看电影','听相声','打篮球')
结果:
看书 下棋 ('看电影', '听相声', '打篮球')
可以看到,h1,接收了看书,h2接收了下棋,其余的三个被*hobby接收了。这样就印证了,上面的注意点:动态参数必须在位置参数后面。
还有一个默认值参数,也是在形参处的,它的位置在哪呢?假设,输入爱好的时候输入一下性别,要看一下,男生和女生的爱好的不同之处。性别可以先给定一个默认值- -‘男’。如果是女生就输入,男生,就不输入了,使用默认值。前面说过了,默认值参数,需要放在最后,现在也是先放在最后,看看下面的代码:
def hobby(h1,h2,*hohhy,gender = '男'):
print(h1,h2,hohhy,gender)
hobby('看书', '下棋', '看电影','听相声','打篮球')
结果:
看书 下棋 ('看电影', '听相声', '打篮球') 男
可以看到,一切OK,可以正常运行,但是这个性别可不可以放在别的地方呢?再来看:
def hobby(h1,h2,gender = '男',*hohhy):
print(h1,h2,hohhy,gender)
hobby('看书', '下棋', '看电影','听相声','打篮球')
结果:
看书 下棋 ('听相声', '打篮球') 看电影
可以看到,没有报错,但是仔细看会发现,有问题,本该打印男的地方,出现了‘看电影’,这是按照位置参数的方式,gender接收了‘看电影’。如果把‘看电影’改 为‘男’或 者‘女’,其实也是可以的。但这样,设置的默认值就没有意义了。如果既想使用默认值,还想可以改变这个参数的值,那么,就把默认值参数放在最后。不需要 修改时,不传 参,需要修改时,使用关键字传参的方式。
因此可以得到一个小结论:位置参数, *动态参数, 默认值参数。
2. 动态接收关键字参数
在python中可以动态的位置参数, 但是*这种情况只能接收位置参数⽆法接收关键字参数。在python中使⽤**就可以接收动态关键字参数了。看下面代码:
def hobby(**hohhy_info):
print(hohhy_info)
hobby(hohhy = ['看书', '下棋', '看电影', '听相声', '打篮球'], name='Tom',gender='男')
结果:
{'hohhy': ['看书', '下棋', '看电影', '听相声', '打篮球'], 'name': 'Tom', 'gender': '男'}
这就是动态接收关键字参数。接收后,打印出来的是个字典。
问题,又来了,现在形参有四种情况了,分别是:位置参数,*动态参数,**动态参数,默认值参数。这四个的顺序是什么呢?
顺序的问题, 在函数调⽤的时候, 如果先给出关键字参数, 则整个参数列表会报错.
def func(a, b, c, d):
print(a, b, c, d)
# 关键字参数必须在位置参数后⾯, 否则参数会混乱
func(1, 2, c=3, 4)
所以关键字参数必须在位置参数后面. 由于实参是这个顺序. 所以形参接收的时候也是这个顺序. 也就是说位置参数必须在关键字参数前面。 类似的,动态接收关键字参数也要在后面,所以最终顺序:位置参数 > *args > 默认值参数 > **kwargs
有一个现象,就是位置参数动态传递过去后,被*args打包成了一个元组,动态接收关键字参数时,**args又把所有的参数打包成了一个字典。那么要是在调用的地方给参数带 着“*”呢?作用与形参的地方正好相反,实参的地方使用“*”,是打散这个实参,然后再传递过去。
def hobby(*hohhy):
print(hohhy)
hobby(*['看书', '下棋', '看电影', '听相声', '打篮球'])
结果:
('看书', '下棋', '看电影', '听相声', '打篮球')
可以看到,这段代码把列表开始打散了,然后再组成一个元组。再看“**”打散的效果:
def hobby(**info):
print(info)
hobby(**{'name':'Tom','age':23})
结果:
{'name': 'Tom', 'age': 23}
看到结果了,很懵吧?怎么会是一样的呢?这是没有处理啊。其实已经处理了:
def hobby(**info):
print(info)
hobby(**{'name': 'Tom', 'age': 23})
hobby(name='Tom', age = 23)
结果:
{'name': 'Tom', 'age': 23}
{'name': 'Tom', 'age': 23}
可以看到,两种写法最后的结果是一样的。其实,第一种打散,就是打散成第二种方式了。这个过程是内部程序做的,看不到。还有一个疑问,传进去字典,输出的还是字典, 也有什么用呢?再看一段代码:
def hobby(**info):
print(info)
hobby(**{'name': 'Tom', 'age': 23},**{'gender':'男','edu':'本科'})
hobby(**{'name': 'Tom', 'age': 23},gender='男',edu='本科')
结果:
{'name': 'Tom', 'age': 23, 'gender': '男', 'edu': '本科'}
{'name': 'Tom', 'age': 23, 'gender': '男', 'edu': '本科'}
可以看到两种方式的传参后,最后的结果是一样的效果。这就是“**”打散的作用了,可以合并两个字典,也可以把零散的关键字传参,打包进字典在中去。
二、命名空间
1.这里介绍几个名词:全局命名空间,局部命名空间,内置命名空间。
1) 全局命名空间--> 我们直接在py⽂件中, 函数外声明的变量都属于全局命名空间
2) 局部命名空间--> 在函数中声明的变量会放在局部命名空间
3) 内置命名空间--> 存放python解释器为我们提供的名字, list, tuple, str, int这些都是内置命名空间
加载顺序:
a. 内置命名空间
b. 全局命名空间
c. 局部命名空间(函数被执⾏的时候)
取值顺序:
a. 局部命名空间
b. 全局命名空间
c. 内置命名空间
2.作⽤域: 作⽤域就是作⽤范围, 按照⽣效范围来看分为全局作⽤域和局部作⽤域
作⽤域命名空间:
a. 全局作⽤域: 全局命名空间 + 内置命名空间
b. 局部作⽤域: 局部命名空间
我们可以通过globals()函数来查看全局作⽤域中的内容, 也可以通过locals()来查看局部作⽤域中的变量和函数信息。
a = 10
def func():
a = 40
b = 20
def abc():
print("哈哈")
print(a, b) # 这⾥使⽤的是局部作⽤域 (1)
print(globals()) # 打印全局作⽤域中的内容(2)
print(locals()) # 打印局部作⽤域中的内容(3)
func()
结果:
40 20 #(1)打印的结果
{'__name__': '__main__', '__doc__': None, '__package__': None, '__loader__': <_frozen_importlib_external.SourceFileLoader object at 0x00000094AF26C240>,
'__spec__': None, '__annotations__': {}, '__builtins__': <module 'builtins' (built-in)>, '__file__': 'D:/pyworkspace/day11_大作业/test.py',
'__cached__': None, 'a': 10, 'func': <function func at 0x00000094AF1B1EA0>} #(2)打印的结果
{'abc': <function func.<locals>.abc at 0x00000094AF3310D0>, 'b': 20, 'a': 40} #(3)打印的结果
通过结果,可以看出(2)和(3)打印的是两个字典,尤其(3)中可以看到 “ 'b': 20, 'a': 40”,这是标准的键值对的形式。globals()和locals(),两个函数时Python内置的, 为的就是查看,当前作用域中变量有哪些。globals(),查看的是.py文件中的全局的,locals()查看的是当前作用域中,变量有哪些。
三. 函数的嵌套
1. 只要遇⻅了()就是函数的调⽤. 如果没有()就不是函数的调⽤
2. 函数的执⾏顺序:走到某个函数时,会先把代码加载到内存中,然后等待着被调用。被调用到后,再去执行函数里面的代码。
函数的调用:
def fun1():
print(111)
def fun2():
print(222)
fun1()
fun2()
函数的嵌套:
def fun2():
print(222)
def fun3():
print(666)
print(444)
fun3()
print(888)
print(33)
fun2()
print(555)
函数的嵌套的意义在哪呢?后面的闭包和装饰器会显露出来,拭目以待。
四. 关键字global和nonlocal
首先我们写这样⼀个代码, ⾸先在全局声明⼀个变量, 然后再局部调⽤这个变量, 并改变这个变量的值。
a = 100
def func():
global a # 加了个global表示不再局部创建这个变量了. ⽽是直接使⽤全局的a
a += 28
print(a)
func()
print(a)
a = 10
def func1():
a = 20
def func2():
nonlocal a
a = 30
print(a)
func2()
print(a)
func1()
结果:
加了nonlocal
30
30
不加nonlocal
30
20
nonlocal的作用就是使用离本作用域最近的上一层的变量,上一层没有,再继续上一层,知道找到最外层的函数,如果都没有,就会报错。
总结一下,这两个关键字的调用顺序。
global:局部作用域-->全局作用域-->内置作用域
nonlocal:最内层函数-->上一层函数-->...-->最外层函数
上面的代码这样写,运行后结果:128,128。但是如果把global这一行去掉后,直接报红。根本没有办法编译,在语法上就是错误的。那为什么带着global就可以呢?因为,使用这个关键字global后,就把全局的a带到了函数内,在函数内就可以修改这个a的值了。这就是global的作用:在局部使用并且可以修改全局的变量。也可以说是不再使⽤局部作⽤域中的内容了, ⽽改⽤全局作⽤域中的变量。
接下来再看nonlocal,表⽰在局部作⽤域中, 调⽤⽗级命名空间中的变量。