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


    . 函数参数--动态传参

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

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

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

  • 相关阅读:
    vbscript 语言通过序列和ADODB实现取号不重复
    arcgisserver成功发布服务后,浏览服务,无地图显示
    GUID的获取
    EasyUi 表格自适应宽度
    接口隔离原则
    依赖倒置原则
    开放封闭原则
    单一职责原则
    python-函数基础
    python -流程控制
  • 原文地址:https://www.cnblogs.com/asia-yang/p/10087679.html
Copyright © 2020-2023  润新知