暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

连载|想用Python做自动化测试?函数的参数传递机制及变量作用域

明说软件测试 2020-07-23
350

 这一节有点难。看不懂没关系。继续往后学,回头再来看。

10.6 函数参数传递的机制

10.6.1 值传递与引用传递

编程语言的参数传递机制通常有两种:

  • 值传递

    • 拷贝参数的值,然后传递给函数里的新变量。这样,原变量和新变量之间互相独立,互不影响。

  • 引用传递

    • 把参数的引用传给新的变量,这样,原变量和新变量就会指向同一块内存地址。如果改变了其中任何一个变量的值,那么另外一个变量也会相应地随之改变。

10.6.2 Python 参数传递是赋值传递

这里首先引用 Python 官方文档中的一段说明:

“Remember that arguments are passed by assignment in Python. Since assignment just creates references to objects, there’s no alias between an argument name in the caller and callee, and so no call-by-reference per Se.”

准确地说,Python 的参数传递是赋值传递 (pass by assignment),或者叫作对象的引用传递(pass by object reference)。Python 里所有的数据类型都是对象,所以参数传递时,只是让新变量与原变量指向相同的对象而已,并不存在值传递或是引用传递一说。不要与引用传递弄混了,引用传递的是一个地址。

  • 不可变对象作为参数

    def my_func1(b):
    b = 2 #③函数执行时,变量b指向对象2


    a = 1 # ①实参变量a指向对象1
    my_func1(a) #②因为是赋值传递,变量 b 也指向了变量a指向的对象 1
    a # ④函数执行完成后,变量a依然指向对象1
    1

    代码中注释里面的序号是程序的执行顺序。函数被调用时(②),因为是赋值传递,所以函数形参变量b指向了 实参变量 a  所指向的对象1。但当代码执行到 b = 2 时(③),系统会重新创建一个值为 2 的新对象,并让 b 指向它;而 a 仍然指向 1 这个对象。所以,a 的值不变,仍然为 1。

    稍作改变,让函数返回新变量,赋给 a,再来看看。

      def my_func2(b):
      b = 2 #③函数执行时,变量b指向对象2
      return b # ④关键,函数返回一个指向对象2的变量


      a = 1 # ①实参变量a指向对象1
      a = my_func2(a) # ②将my_func返回值(指向对象2)赋值给了a,所以a 也指向了对象2
      a
      2
      • 可变对象作为参数

      当可变对象当作参数传入函数里的时候,改变可变对象的值,就会影响所有指向它的变量。

        def my_func3(l2):
        l2.append(4) # ③在原对象[1,2,3]上修改,对象变为 [1,2,3,4]


        l1 = [1, 2, 3] # ① l1是个可变对象,指向了对象[1,2,3]
        my_func3(l1) #②因为是赋值传递,变量 l2 也指向了变量l1指向的对象 [1,2,3]
        l1 # ④函数执行完成后,l1和l2指向的对象发生了改变
        [1, 2, 3, 4]

        如果多次调用my_func3,那么每次l1得到的结果都不一样,l1不断在列表后面添加元素4。

          def my_func3(l2):
          l2.append(4) # ③在原对象[1,2,3]上修改,对象变为 [1,2,3,4]
          if __name__ == '__main__':
          l1 = [1, 2, 3] # ① l1是个可变对象,指向了对象[1,2,3]
          my_func3(l1) #②因为是赋值传递,变量 l2 也指向了变量l1指向的对象 [1,2,3,4]
          print(l1)
          my_func3(l1) # ②因为是赋值传递,变量 l2 也指向了变量l1指向的对象 [1,2,3,4,4]
          print(l1)
          my_func3(l1) # ②因为是赋值传递,变量 l2 也指向了变量l1指向的对象 [1,2,3,4,4,4]
          print(l1)

          不改变原来可变对象的值,而是生成新对象,结果与上面不同。看下下面的:

            def my_func4(l2):
            l2 = l2 + [4] # 创建了一个“末尾加入元素 4“的新对象,原对象没变


            l1 = [1, 2, 3]
            my_func4(l1)
            l1
            [1, 2, 3] # l1对象没变

            实际工程中,我们一般不希望函数内部可以修改外部变量的值,因为可能会导致代码出现异常而且难以排查问题所在。所以通常函数内部重新创建一个新的列表(可变对象),对这个新列表(可变对象)进行操作,而不是操作输入的列表,并增加语句return 语句。再来看一个类似的例子:

              def multiply_2_pure(l):
              new_list = [] # 创建一个新列表
              for item in l:
              new_list.append(item * 2)
              return new_list # 返回新列表

              10.7 函数变量作用域

              10.7.1 局部变量

              定义在函数内部,它的作用域是在函数内部,函数调用结束之后,函数里面保存的信息就被销毁了。比如下面的这个代码示例,连续调用函数4次,每次输出的值都是4,即3+1,这说明每次调用fun函数之后,函数内部定义的局部变量num就被销毁了,再次被调用时将被重新初始化为1,那如果要保存函数的局部变量,怎么办呢?后面介绍的“闭包”就可以完成这个任务。

                def fun(step):
                num = 1
                num += step
                print(num)


                i = 1
                while i < 5:
                fun(3)
                i += 1

                10.7.2 全局变量

                定义在整个文件层次上,如下MIN_VALUE和MAX_VALUE这两个变量就是全局变量,通常大写。

                  MIN_VALUE = 1
                  MAX_VALUE = 10
                  def validation_check(value):
                  if value < MIN_VALUE or value > MAX_VALUE:
                  raise Exception('validation check fails')

                  不能在函数内部随意改变全局变量的值,一定要改变的话,要在全局变量前面加个global。global的作用就是在“函数局部作用域”内声明表示一个全局变量,从而可以在函数内部修改全局变量的值(否则只能访问不能修改)。

                    MIN_VALUE = 1
                    MAX_VALUE = 10
                    def validation_check(value):
                    global MIN_VALUE # 表示函数内部的这个MIN_VALUE是来自全局变量MIN_VALUE
                    MIN_VALUE += 1
                    print(MIN_VALUE)
                    validation_check(5) # 输出2

                    10.7.3 局部变量覆盖全局变量

                    如果遇到函数内部局部变量和全局变量同名的情况,那么在函数内部,局部变量会覆盖全局变量。

                      MIN_VALUE = 1
                      MAX_VALUE = 10
                      def validation_check(value):
                      MIN_VALUE = 3
                      print(MIN_VALUE) # 输出3
                      validation_check(5)

                      10.8 函数的嵌套

                      函数可以嵌套定义。

                      • 形式1,在函数内部直接调用定义的内部函数

                        def outer(name):
                        num = 100


                        def inner(weight, height, age):
                        weight += 1
                        height += 1
                        age += 1
                        print(name, weight, height, age)


                        inner(100, 200, 300) # 直接调用“内部函数”




                        outer('盒子')
                        • 形式2,在函数内部return定义的内部函数

                          def outer(name):
                          num = 100


                          def inner(weight, height, age):
                          weight += 1
                          height += 1
                          age += 1
                          print(name, weight, height, age)


                          return inner # 返回函数对象




                          alias = outer('盒子') # 调用外部函数,得到inner对象
                          alias(100, 200, 300) # 调用inner函数对象,传入三个参数
                          • 闭包,外部函数执行完后,外部函数的参数仍然会被内部函数记住。

                            def nth_power(exponent):  # 外部函数
                            def exponent_of(base):
                            return base ** exponent # 内部函数使用了外部函数的参数


                            return exponent_of # 外部函数返回内部函数exponent_of




                            square = nth_power(2) # exponent_of赋值给变量square,square记住了外部函数的exponent参数值2
                            cube = nth_power(3) # exponent_of赋值给变量cube,记住了外部函数的exponent参数值3
                            print(square(9)) # 计算9的平方
                            print(cube(9)) # 计算9的立方

                            nth_power(2)被调用时,exponent被赋值为2,并且保存到exponent_of函数对象中。当square(9)被调用时,其实就是调用exponent_of函数,这个函数内部base是9,exponent是2,因此square(9)的返回值是81。

                            通过Pycharm的Debug功能来看一下:

                            从这里可以看出,代码执行到print(square(9))这一行时,nth_power(2)已经执行结束了,但是它的局部变量exponent的信息却被保存了下来,从调试窗口可以看到它的值是2,从而当进入内部函数exponent_of时,就能够计算9**2
                            了,这就是“闭包”的最大的作用——保存局部信息不被销毁。

                            函数特点总结:

                            • 函数也是对象

                            • 函数对象可以赋值给变量

                            • 函数对象可以作为参数传递给另外的函数

                            • 函数对象可以作为另外一个函数的返回值

                            • 函数可以嵌套定义



                            文章转载自明说软件测试,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

                            评论