?. 函数参数--动态传参
如果我们需要给?个函数传参, ?参数?是不确定的. 或者我给?个函数传很多参数, 我的形参就要写很多, 很?烦, 怎么办呢. 我们可以考虑使?动态参数.
形参的第三种: 动态参数。 动态参数分成两种:
?先我们先回顾?下位置参数, 位置参数, 按照位置进?传参。
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,表?在局部作?域中, 调??级命名空间中的变量。
原文:https://www.cnblogs.com/asia-yang/p/10087679.html