zoukankan      html  css  js  c++  java
  • Python函数的返回值和作用域

    函数的返回值和作用域

    1、返回值

    def guess(x):
        if x > 3:
            return "> 3"
        else:
            return "<= 3"
    print(guess(10))

      1> Python 函数使用 return 语句返回 "返回值”
      2> 所有函数都有返回值,如果没有 return 语句,隐式调用 return None
      3> return 语句并不一定是函数的语句块的最后一条语句
      4> 一个函数可以存在多个 return 语句,但是只有一条可以被执行。如果没有一条 return 语句被执行到,隐式调用 return None
      5> 如果有必要,可以显示调用return None,可以简写为return
      6> 如果函数执行了 return 语句,函数就会返回,当前被执行的 return 语句之后的其它语句就不会被执行了
      7> 返回值的作用:结束函数调用、返回 "返回值”

    2、能一次返回多个值嘛?

    def showvalues():
        return 1, 3, 5
    print(showvalues())    # 返回 (1, 3, 5)

      函数不能同时返回多个值
      return 1, 3, 5 看似返回多个值,隐式的被 python 封装成了一个元组
      x, y, z = showvalues() 使用解构提取返回值更为方便

    3、函数作用域**

    3.1 作用域

      一个标识符的可见范围,这就是标识符的作用域,一般常说变量的作用域。

    x = 20
    def fn():
        x = 100  # x 的作用域:当前函数
    fn()
    print(x)     # x = 20

      注意:每一个函数都会开辟一个作用域。

    3.2 作用域分类

      全局作用域:
        在整个程序运行环境中都可见
        全局作用域中的变量称为全局变量

      局部作用域:
        在函数、类等内部可见
        局部作用域中的变量称为局部变量,其使用范围不超过其所在局部作用域

    # 局部变量
    def fn1():
        x = 1    # 局部作用域,x 为局部变量,使用范围在 fn1 内
    
    def fn2():
        print(x) # x 能打印吗?不能
    
    print(x)     # x 能打印吗?不能
    # 全局变量
    x = 5        # 全局变量,也在函数外定义
    def foo():
        print(x) # 可见吗?可以
    foo()

      一般来讲外部作用域变量在函数内部可见,可以使用
      反过来,函数内部的局部变量,不能在函数外部看到

    4、函数嵌套

      在一个函数中定义另一个函数

    def outer():
        def inner():
            print('inner')
        print('outer')
        inner()
    outer()
    inner()    # 不可以

      内部函数 inner 不能在外部直接使用,会抛出 NameError 异常,因为它在函数外部不可见。
      其实,inner 不过就是一个标识符,就是一个函数 outer 内部定义的变量而已。

    5、嵌套函数的作用域

    def outer():
        o = 65    # 局部变量、本地 local 变量、临时变量
        def inner():
            o = 97
            print('inner', o)
        print('outer 1 ', o)
        inner()
        print('outer 2 ', o)
    outer()       # 1:outer 1  65    2:inner 97    3:outer 2  65

      外层变量在内部作用域可见。
      内层作用域中,如果定义了和外层相同的变量,相当于在当前函数作用域中重新定义了一个新的变量,这个内层变量并不能覆盖掉外部作用域中的变量。

    6、一个赋值语句的问题

    x = 100
    def fn():
        y = x + 200
        print(y)
    fn()
    x = 100
    def fn():
        x += 1    # 报错! 赋值即定义,即 x = x + 1 (局部变量 = 局部变量 + 1)!
        print(x)
    fn()
    x = 100
    def fn():
        print(x)  # 报错!该步执行不了!
        x += 1    # 只要在该作用域内赋值定义('=')局部变量,在该作用域内的所有该变量都为局部变量!
        print(x)
    fn()

      能否解决呢?可以,使用 global 语句

    x = 100
    def fn():
        global x  # 声明全局变量
        print(x)  # 100
        x += 1
        print(x)  # 101
    fn()
    print(x)      # 101

      注意:全局变量一般情况不推荐修改,一旦在作用域中使用 global 声明全局变量,那么相当于在对全局变量赋值、定义。

      global 使用原则:

        1> 外部作用域变量会在内部作用域可见,但也不要在这个内部的局部作用域中直接使用,因为函数的目的就是为了封装,尽量与外界隔离。
        2> 如果函数需要使用外部全局变量,请尽量使用函数的形参定义,并在调用传实参解决。
        3> 一句话:不用 global,学习它就是为了深入理解变量作用域。

    # 不建议直接传入全局变量!
    y = []
    
    def foo():    # x 就是标识符,就是变量,就是本地变量
        y.append(1)
    
    foo()
    foo()
    print(y)
    # 建议使用传参的方式,在函数内使用全局变量
    y = []
    
    def foo(x):    # x 就是标识符,就是变量,就是本地变量
        x.append(1)
    
    foo(y)
    foo(y)
    print(y)

    7、闭包**

      自由变量:未在本地作用域中定义的变量。例如定义在内层函数外的外层函数的作用域中的变量
      闭包:就是一个概念,出现在嵌套函数中,指的是内层函数引用到了外层函数的自由变量,就形成了闭包。很多语言都有这个概念,最熟悉就是 JavaScript。

    # python 2 实现闭包
    def counter():
        c = [0]
        def inc():
            c[0] += 1  # 是赋值即定义嘛?不是!是修改值
            return c[0]
        return inc     # 返回标识符,即函数对象
    
    m = counter()
    m()                # 调用函数 inc(),但是 c 消亡了嘛?没有,内层函数没有消亡,c 不消亡(闭包)
    m()
    m()
    print(m())
    # 不推荐使用 global !
    def counter():
        global c
        c = 0
        def inc():
            global c
            c += 1        # 不是闭包!
            return c
        return inc
    
    m = counter()
    m()
    m()
    m()
    print(m())
    # 推荐使用 nonlocal,python 3 实现闭包
    def counter():
        c = 0
        def inc():
            nonlocal c    # 非当前函数的本地变量,当前函数之外的任意层函数的变量,绝非 global
            c += 1        # 是闭包吗?是!
            return c
        return inc
    
    m = counter()
    m()
    m()
    m()
    print(m())

      nonlocal 语句:将变量标记为不在本地作用域定义,而是在上级的某一级局部作用域中定义,但不能是全局作用域中

    8、默认值的作用域

    def foo(x=1):
        x += 1
        print(x)
    foo()    # 2
    foo()    # 2
    
    def bar(x=[]):    # x = [],引用类型
        x.append(1)   # [1]
        print(x)
    bar()    # [1]
    bar()    # [1, 1]

      为什么上列 bar 函数第二次调用打印的是 [1, 1]?
        因为函数也是对象,每个函数定义被执行后,就生成了一个函数对象和函数名这个标识符关联。
        python 把函数的默认值放在了函数对象的属性中,这个属性就伴随着这个函数对象的整个生命周期。

    # 查看 foo.__defaults__ 属性,它是个元组
    def bar(x=[]):
        x.append(1)
        print(x)
    print(bar.__defaults__)
    bar()    # [1]
    print(bar.__defaults__)
    bar()    # [1, 1]
    print(bar.__defaults__)
    
    # 执行结果:
    ([],)
    [1]
    ([1],)
    [1, 1]
    ([1, 1],)    # 元组不变,记录的是地址,引用类型变化
    def foo(x, m=123, n='abc'):
        m=456
        n='def'
        print(x)
    print(foo.__defaults__)    # (123, 'abc')
    foo('yang')
    print(foo.__defaults__)    # (123, 'abc')
    def foo(x, m=123, *, n='abc', t=[1,2]):
        m=456
        n='def'
        t.append(12)
        #t[:].append(12)    # t[:],全新复制一个列表,避免引用计数
        print(x, m, n, t)
    
    print(foo.__defaults__, foo.__kwdefaults__) #(123,) {'n': 'abc', 't': [1, 2]}
    foo('yang')
    print(foo.__defaults__, foo.__kwdefaults__) #(123,) {'n': 'abc', 't': [1, 2, 12]}
    def x(a=[]):
        a = a + [5]         # 加法的本质:返回新列表、新地址;赋值即定义
    print(x.__defaults__)   # ([],)
    x()
    x()
    print(x.__defaults__)   # ([],)
    
    def y(a=[]):
        a += [5]            # += 即 extend => a.extend([5])
    print(y.__defaults__)   # ([],)
    y()
    y()
    print(y.__defaults__)   # ([5, 5],)
    
    
    # 列表的 + 和 += 的区别:
    # + 表示两个列表合并并返回一个全新的列表。
    # += 表示,就地修改前一个列表,在其后追加后一个列表,就是 extend 方法。
    # 例:
    l1 = [1, 2]
    l2 = [3, 4]
    l3 = l1 + l2
    print(id(l1), id(l2), id(l3))
    l3 += l2    # 就地修改
    print(id(l3))
    
    执行结果:
    2279905055304 2279905055816 2279906334216
    2279906334216

    9、变量名解析原则 LEGB**

      Local,本地作用域、局部作用域的 local 命名空间,函数调用时创建,调用结束消亡。
      Enclosing,Python2.2 时引入了 嵌套函数,实现了闭包,这个就是嵌套函数的外部函数的命名空间
      Global,全局作用域,即一个模块的命名空间。模块被 import 时创建, 解释器退出时消亡。
      Build-in,内置模块的命名空间,生命周期从 python 解释器启动时创建到解释器退出时消亡。例如 print(open),print 和 open 都是内置的变量。

      所以一个名词的查找顺序就是 LEGB

      

    10、函数的销毁

      定义一个函数就是生成一个函数对象,函数名指向的就是函数对象。
      可以使用del语句删除函数,使其引用计数减 1。
      可以使用同名标识符覆盖原有定义,本质上也是使其引用计数减 1。
      Python程序结束时,所有对象销毁。
      函数也是对象,也不例外,是否销毁,还是看引用计数是否减为 0。

  • 相关阅读:
    【Netty之旅四】你一定看得懂的Netty客户端启动源码分析!
    Netty之旅三:Netty服务端启动源码分析,一梭子带走!
    【原创】经验分享:一个Content-Length引发的血案(almost....)
    Netty之旅二:口口相传的高性能Netty到底是什么?
    Java解压和压缩带密码的zip文件过程详解
    SQLServer安装教程(史上最详细版本)
    26.Vue技术栈开发实战-项目部署
    25.Vue技术栈开发实战-多Tab页开发
    6-6 创建产品卡片组件(1)
    6-5 创建垂直网格组件
  • 原文地址:https://www.cnblogs.com/zyybky/p/12747878.html
Copyright © 2011-2022 走看看