Python 函数之二(返回值、作用域、LEGB、销毁)

2021-01-18  本文已影响0人  Alexander_Zz

一、函数返回值

先看几个例子

# return 语句之后可以执行么?
def showplus(x):
    print(x)
    return x + 1
    print('~~end~~')

showplus(5)

# 多条 return 语句都会执行么?
def showplus(x):
    print(x)
    return x + 1
    return x + 2

showplus(5)

# 下例多个 return 可执行么?
def guess(x):
    if x > 3:
        return "> 3"
    else:
        return "<= 3"

print(guess(10))

# 下面函数执行的结果是什么
def fn(x):
    for i in range(x):
        if i > 3:
            return i
    else:
        print("{} is not greater than 3".format(x))

print(fn(5))   # 打印 4
print(fn(3))   # 打印 None

总结

能够一次返回多个值么?

def showvalues():
    return 1, 3, 5

showvalues()

二、函数 作用域

2.1 作用域

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

def foo():
    x = 100

print(x)   # 可以访问到么?

上例中 x 不可被访问到,会抛出异常 NameError: name 'y' is not defined,原因在于函数是一个封装,它会开辟一个 作用域x 变量被限制在这个作用域中,所以在函数外部 x 变量 不可见

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

2.2 作用域分类
# 局部变量
def fn1():
    x = 1   # 局部作用域,x 为局部变量,使用范围在 fn1 内
    
def fn2():
    print(x)
    
print(x)
# 全局变量
x = 5   # 全局变量,也在函数外定义
def foo():
    print(x)

foo()
2.3 函数嵌套

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

def outer():
    def inner():
        print("inner")
    print("outer")
    inner()
outer()   # 可行么?
inner()   # 可行么?

内部函数 inner 不能在外部直接使用,会抛 NameError 异常,因为它在函数外部不可见

其实, inner 不过就是一个标识符,就是一个函数 outer 内部定义的变量而已

嵌套结构作用域
对比下面嵌套结构,代码执行的效果

def outer1():
    o = 65
    def inner():
        print("inner {}".format(o))
        print(chr(o))
        
    inner()
    print("outer {}".format(o))
    
outer1()

def outer2():
    o = 65
    def inner():
        o = 97
        print("inner {}".format(o))
        print(chr(o))
        
    inner()
    print("outer {}".format(o))
    
outer2()

从执行的结果来看

2.4 一个复制语句的问题

再看下例


示例.png

仔细观察函数 2 返回的错误指向 x += 1,原因是什么呢?

x = 5
def foo():
    x += 1
foo()   # 报错如下
示例.png

原因分析

如何解决这个常见问题?

2.5 global 语句
x = 5
def foo():
    global x   # 全局变量
    x += 1
    print(x)
foo()

若全局作用域中没有 x 定义会怎样?
注意,下面实验若在 ipython、jupyter 中做,上下文运行环境中有可能有 x 的定义,稍微不注意,就测试不出效果

# 有错么?
def foo():
    global x
    x += 1
    print(x)
foo()

# 有错么?
def foo():
    global x
    x = 10
    x += 1
    print(x)
foo()
print(x)   # 可以么?

使用 global 关键字定义的变量,虽然在 foo 函数中声明,但是这将告诉当前 foo 函数作用域,这个 x 变量将使用外部全局作用域中的 x

即使实在 foo 中又写了 x=10,也不会在 foo 这个局部作用域中定义局部变量 x

使用了 globalfoo 中的 x 不再是局部变量了,它是全局变量

总结

global 使用原则

2.6 闭包

自由变量:未在本地作用域中定义的变量,例如定义在内层函数外的外层函数的作用域中的变量
闭包:就是一个概念,出现在嵌套函数中,至的是 内层函数引用到了外层函数的自由变量,就形成了闭包。

def counter():
    c = [0]
    def inc():
        c[0] += 1
        return c[0]
    return inc
foo = counter()
print(foo(), foo())
c = 100
print(foo())

上例代码分析

这是 Python 2 中实现闭包的方式,Python 3 还可使用 nonlocal 关键字

def counter():
    count = 0
    def inc():
        count += 1
        return count
    return inc

foo = counter()
print(foo(), foo())

上例一定出错,使用 global 可解决

def counter():
    global count
    count = 0
    def inc():
        global count
        count += 1
        return count
    return inc

foo = counter()
print(foo(), foo())

上例使用 global 解决,这是全局变量的实现,而不是闭包了
若对这个普通变量使用闭包,Python 3 中可使用 nonlocal 关键字

2.7 nonlocal 语句

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

def counter():
    count = 0
    def inc():
        nonlocal count   # 声明变量 count 不是本地变量
        count += 1
        return count
    return inc

foo = counter()
print(foo(), foo())

count 是外层函数的局部变量,被内部函数引用
内部函数使用 nonlocal 关键字声明 count 变量在上级作用域而非本地作用域中定义
代码中内层函数应用外部局部作用域中的自由变量,形成闭包

示例.png

上例是错误的,nonlocal 声明变量 a 不在当前作用域,但是往外就是全局作用域了,所以报错

三、默认值的作用域

示例.png
示例.png

为何第二次调用 foo() 打印的值 [1, 1]

def foo(xyz=[], m=123, n='abc'):
    xyz.append(1)
    print(xyz)
    
print(id(foo), foo.__defaults__)
foo()
print(id(foo), foo.__defaults__)
foo()
print(id(foo), foo.__defaults__)

函数地址并没有变,就是说 foo() 这个函数对象没有变过,调用它,他的属性 __defaults__ 中使用元祖保存默认值 xyz 的默认值为引用类型,引用类型的元素变动,并不是元祖的变化

# 非引用类型缺省值
def foo(xyz, m=123, n='abc'):
    m = 456
    n = 'def'
    print(xyz)
    
print(foo.__defaults__)
foo('rookie')
print(foo.__defaults__)

属性 __defaults__ 中使用元祖保存所有位置参数默认值,不会因为在函数体内改变了局部变量(形参)的值而发生改变

# keyword-only 参数的缺省值
def foo(xyz, m=123, *, n='abc', t=[1, 2]):
    m = 456
    n = 'def'
    t.append(300)
    print(xyz, m, n, t)
    
print(foo.__defaults__, foo.__kwdefaults__)
foo('rookie')
print(foo.__defaults__, foo.__kwdefaults__)

属性 __defaults__ 中使用元组保存所有位置参数默认值
属性 __kwdefaults__ 中使用字典保存所有 keyword-only 参数的默认值

def x(a=[]):
    a += [5]
    
print(x.__defaults__)
x()
x()
print(x.__defaults__)

def y(a=[]):
    a = a = [5]
    
print(y.__defaults__)
y()
y()
print(y.__defaults__)

输出结果不一致

四、变量名解析原则 LEGB

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


示例.png

五、函数的销毁

上一篇下一篇

猜你喜欢

热点阅读