设为首页 加入收藏

TOP

Python3基础-Python作用域详述(三)
2018-11-08 12:13:27 】 浏览:574
Tags:Python3 基础 -Python 作用 详述
: x=4 def f2(): def f3(): nonlocal x # f1()的本地 print("f3:",x) # 输出4 x=6 # 修改f1()的本地 f3() print("f2:",x) # 输出6 f2() print("f1:",x) # 输出6 f1()

注意,上面f3()中的nonlocal将x修饰为f1()的本地变量,因为f3()的上一层f2()中没有变量x,所以f2()继承了f1()的变量x,使得f3()修改上一层f2()中的变量,等价于修改f1()中的变量x。

但如果把f1()中的x=4也删除,那么将报错,因为nonlocal无法将变量修饰为全局范围。

所以,nonlocal默认将内层函数中的变量修饰为上一层函数的作用域范围,如果上一层函数中不存在该变量,则修饰为上上层、上上上层直到顶层函数,但不能修饰为全局作用域范围

同样的,只要在内层函数中赋值,就表示声明这个变量的作用域为内层函数作用域范围。所以,下面的代码是错误的:

x=3
def f1():
    x=4
    def f2():
        print(x)
        x=3
    f2()
f1()

下面的代码也是错的:

x=3
def f1():
    x=4
    def f2():
        x += 3
        print(x)
    f2()
f1()

错误信息:

UnboundLocalError: local variable 'x' referenced before assignment

至于原因,前文已经解释的很清楚。

访问外层函数变量的其它方法

在以前的版本中,还没有nonlocal关键字,这时如果要保存外层函数的变量,就需要使用函数参数默认值的方式定义内层函数。

x=3
def f1():
    x=4
    def f2(x=x):
        x += 3
        print("f2:",x)
    x=5
    f2()
    print("f1:",x)
f1()

输出:

f2: 7
f1: 5

上面的f2(x=x)中,等号右边的x来自于f1()中x=4,然后将其赋值给f2()的本地作用域变量x。注意,python的作用域是词法作用域,函数区块的定义位置决定了它看到的变量。所以,尽管调用f2()之前再次对x进行了赋值,f2()函数调用时,f2(x=x)等号右边的x早已经赋值给左边的本地变量x了。它们的关系如下图所示:

避免函数嵌套

一般来说,函数嵌套都只用于闭包(工厂函数),而且是结合匿名函数(lambda)实现的闭包。其它时候,函数嵌套一般都可以改写为非嵌套模式。

例如,下面的嵌套函数:

def f1():
    x=3
    def f2():
        nonlocal x
        print(x)
    f2()
f1()

可以改写为:

def f1():
    x=3
    f2(x)

def f2(x):
    print(x)

f1()

循环内部的函数

当函数位于循环结构中,且这个函数引用了循环控制变量,那么结果可能会出乎意料。

本来以匿名函数(lambda)来解释更清晰,但因为尚未介绍匿名函数,所以这里采用命名函数为例。

下面的代码中,将5个函数作为列表的元素保存到列表list1中。

def f1():
    list1 = []
    for i in range(5):
        def n(x):
            return i+x
        list1.append(n)
    return list1

mylist = f1()
for i in mylist: print(i)
print(mylist[0](2))
print(mylist[2](2))

结果:

<function f1.<locals>.n at 0x02F93660>
<function f1.<locals>.n at 0x02F934B0>
<function f1.<locals>.n at 0x02F936A8>
<function f1.<locals>.n at 0x02F93738>
<function f1.<locals>.n at 0x02F93780>
6
6

从结果中可以看到mylist[0](2)mylist[2](2)的执行结果是一样的,不仅如此,mylist[N](2)的结果也全都一样。换句话说,保存到列表中的各个函数n()中所引用的循环控制变量"i"并没有因为循环的迭代而改变,而且列表中所有函数保存的i的值都是循环的最后一个元素i=4

(注:对于此现象,各语言基本都是如此,本节稍作解释,真正的本质原因在本文的最后一节做了额外的补充解释代码块细述)。

先看下面的例子:

def f1():
    for i in range(5):
        def n():
            print(i)
    return n

f1()()

结果输出4。可见,print(i)的值并没有随循环的迭代过程而改变。

究其原因,是因为def n()只是函数的声明,它不会去查找i的值是多少,所以不会将i的值替换到函数n()的i变量,而是直接保存变量i的地址,当循环结束时,i指向最后一个元素i=4的地址。

当开始调用n()的时候,即f1()(),才会真正开始查找i的值,这时候i指向的正是i=4。

这就像下面的代码一样,在还没有开始调用f()的时候,f()内部的x一直都只是指向它所看见的变量x,而这个x是全局作用域范围。当真正开始调用f()的时候,才会去定位x的指向

x=3
def f():
    print(x)

回到上面循环中的嵌套函数,如果要保证循环的迭代能作用到其内部的函数中,可以采用默认参数值的方式进行赋值:

def f1():
    list1 = []
    for i in range(5):
        def n(x,i=i):
            return i+x
        list1.append(n)
    return list1

上面def n(x,i=i)中的i=i是设置默认参数值,等号右边的i是函数声明时就查找并替换完成的,所以每次循环迭代过程中,等号右边的i都不同,等号左边的参数i的默认值就不同。

再述作用域规则

python的作用域是词法作用域,这意味着函数的定义位置决定了它所看见的变量。除了词法作用域,还有动态作用域,动态作用域意味着函数的调用位置决定了它所看见的变量。关于词法、动态作用域,本文不多做解释,想要了解的话,可以参考一文搞懂:词法作用域、动态作用域、回调函数、闭包

下面是本文开头的问题:

x=1
def f():
    x=3
    g()
    print("f:",x)   # 3

def g():
    print("g:",x)   # 1

f()
print("main:",x)    # 1

对于python的这段代码来说,这里有两个值得注意的地方:

  1. 调用函数之前,理论上要先定义好函数,但这里g()的调用似乎看上去比g()的定义更先
  2. f()中调用g()时,为什么g()输出的是1而不是3

第一个问题在前文已经解释过了,再解释一遍:虽然f()里面有g()的调用语句,但def f()只是声明,但在调用f()之前,是不会去调用g()的。所以,只要f()的调用语句在def g()之后,就是正确的。

第二个问题,python是词

首页 上一页 1 2 3 4 5 下一页 尾页 3/5/5
】【打印繁体】【投稿】【收藏】 【推荐】【举报】【评论】 【关闭】 【返回顶部
上一篇CentOS 7下安装Python3.6 下一篇1.Python是什么

最新文章

热门文章

Hot 文章

Python

C 语言

C++基础

大数据基础

linux编程基础

C/C++面试题目