(十)函数的动态传参,作用域

. 函数参数--动态传参

  如果我们需要给⼀个函数传参, ⽽参数⼜是不确定的. 或者我给⼀个函数传很多参数, 我的形参就要写很多, 很⿇烦, 怎么办呢. 我们可以考虑使⽤动态参数.

     形参的第三种: 动态参数 动态参数分成两种:

  1. 动态接收位置参数

     ⾸先我们先回顾⼀下位置参数, 位置参数, 按照位置进⾏传参。

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)  

      函数的嵌套的意义在哪呢?后面的闭包和装饰器会显露出来,拭目以待。

. 关键字globalnonlocal

     首先我们写这样⼀个代码, ⾸先在全局声明⼀个变量, 然后再局部调⽤这个变量, 并改变这个变量的值。

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,表⽰在局部作⽤域中, 调⽤⽗级命名空间中的变量。

猜你喜欢

转载自www.cnblogs.com/asia-yang/p/10087679.html