变量作用域

阅读: 33180     评论:11

讲到了函数就必须介绍变量的作用域相关。

作用域指的是变量的有效范围。变量并不是在哪个位置都可以访问的,访问权限取决于这个变量是在哪里赋值的,也就是在哪个作用域内的。

通常而言,在编程语言中,变量的作用域从代码结构形式来看,有块级、函数、类、模块、包等由小到大的级别。但是在Python中,没有块级作用域,也就是类似if语句块、for语句块、with上下文管理器等等是不存在作用域概念的,他们等同于普通的语句。

>>> if True:            # if语句块没有作用域
    x = 1   
>>> x
1
>>> def func():         # 函数有作用域
    a = 8   
>>> a
Traceback (most recent call last):
  File "<pyshell#3>", line 1, in <module>
    a
NameError: name 'a' is not defined

从上面的例子中,我们可以发现,在if语句内定义的变量x,可以被外部访问,而在函数func()中定义的变量a,则无法在外部访问。

通常,函数内部的变量无法被函数外部访问,但内部可以访问;类内部的变量无法被外部访问,但类的内部可以。通俗来讲,就是内部代码可以访问外部变量,而外部代码通常无法访问内部变量。

变量的作用域决定了程序的哪一部分可以访问哪个特定的变量名称。Python的作用域一共有4层,分别是:

  • L (Local) 局部作用域
  • E (Enclosing) 闭包函数外的函数中
  • G (Global) 全局作用域
  • B (Built-in) 内建作用域
x = int(2.9)  # 内建作用域,查找int函数

global_var = 0  # 全局作用域
def outer():
    out_var = 1  # 闭包函数外的函数中
    def inner():
        inner_var = 2  # 局部作用域

前面说的都是变量可以找得到的情况,那如果出现本身作用域没有定义的变量,那该如何寻找呢?

Python以L –> E –> G –>B的规则查找变量,即:在局部找不到,便会去局部外的局部找(例如闭包),再找不到就会去全局找,最后去内建中找。如果这样还找不到,那就提示变量不存在的错误。例如下面的代码,函数func内部并没有定义变量a,可是print函数需要打印a,那怎么办?向外部寻找!按照L –> E –> G –>B的规则,层层查询,这个例子很快就从外层查找到了a,并且知道它被赋值为1,于是就打印了1。

a = 1

def func():
    print(a)

全局变量和局部变量

定义在函数内部的变量拥有一个局部作用域,被叫做局部变量,定义在函数外的拥有全局作用域的变量,被称为全局变量。(类、模块等同理)

所谓的局部变量是相对的。局部变量也有可能是更小范围内的变量的外部变量。

局部变量只能在其被声明的函数内部访问,而全局变量可以在整个程序范围内访问。调用函数时,所有在函数内声明的变量名称都将被加入到作用域中。

a = 1               # 全局变量

def func():
    b = 2           # 局部变量
    print(a)        # 可访问全局变量a,无法访问它内部的c

    def inner():
        c = 3       # 更局部的变量
        print(a)    # 可以访问全局变量a
        print(b)    # b对于inner函数来说,就是外部变量
        print(c)

global和nonlocal关键字

我们先看下面的例子:

total = 0                        # total是一个全局变量

def plus( arg1, arg2 ):
    total = arg1 + arg2          # total在这里是局部变量.
    print("函数内局部变量total=  ", total)
    print("函数内的total的内存地址是: ", id(total))
    return total

plus(10, 20)
print("函数外部全局变量total= ", total)
print("函数外的total的内存地址是: ", id(total))

很明显,函数plus内部通过total = arg1 + arg2语句,新建了一个局部变量total,它和外面的全局变量total是两码事。而如果我们,想要在函数内部修改外面的全局变量total呢?使用global关键字!

global:指定当前变量使用外部的全局变量

total = 0                        # total是一个全局变量

def plus( arg1, arg2 ):
    global total    # 使用global关键字申明此处的total引用外部的total
    total = arg1 + arg2          
    print("函数内局部变量total=  ", total)
    print("函数内的total的内存地址是: ", id(total))
    return total

plus(10, 20)
print("函数外部全局变量total= ", total)
print("函数外的total的内存地址是: ", id(total))

打印结果是:

函数内局部变量total=   30
函数内的total的内存地址是:  503494624
函数外部全局变量total=  30
函数外的total的内存地址是:  503494624

我们再来看下面的例子:

a = 1
print("函数outer调用之前全局变量a的内存地址: ", id(a))

def outer():
    a = 2
    print("函数outer调用之时闭包外部的变量a的内存地址: ", id(a))
    def inner():
        a = 3
        print("函数inner调用之后闭包内部变量a的内存地址: ", id(a))
    inner()
    print("函数inner调用之后,闭包外部的变量a的内存地址: ", id(a))
outer()
print("函数outer执行完毕,全局变量a的内存地址: ", id(a))

如果你将前面的知识点都理解通透了,那么这里应该没什么问题,三个a各是各的a,各自有不同的内存地址,是三个不同的变量。打印结果也很好的证明了这点:

函数outer调用之前全局变量a的内存地址:  493204544
函数outer调用之时闭包外部的变量a的内存地址:  493204576
函数inner调用之后闭包内部变量a的内存地址:  493204608
函数inner调用之后,闭包外部的变量a的内存地址:  493204576
函数outer执行完毕,全局变量a的内存地址:  493204544

那么,如果,inner内部想使用outer里面的那个a,而不是全局变量的那个a,怎么办?用global关键字?先试试看吧:

a = 1
print("函数outer调用之前全局变量a的内存地址: ", id(a))
def outer():
    a = 2
    print("函数outer调用之时闭包外部的变量a的内存地址: ", id(a))
    def inner():
        global a   # 注意这行
        a = 3
        print("函数inner调用之后闭包内部变量a的内存地址: ", id(a))
    inner()
    print("函数inner调用之后,闭包外部的变量a的内存地址: ", id(a))
outer()
print("函数outer执行完毕,全局变量a的内存地址: ", id(a))

运行结果如下,很明显,global使用的是全局变量a。

函数outer调用之前全局变量a的内存地址:  494384192
函数outer调用之时闭包外部的变量a的内存地址:  494384224
函数inner调用之后闭包内部变量a的内存地址:  494384256
函数inner调用之后,闭包外部的变量a的内存地址:  494384224
函数outer执行完毕,全局变量a的内存地址:  494384256

那怎么办呢?使用nonlocal关键字!它可以修改嵌套作用域(enclosing 作用域,外层非全局作用域)中的变量。将global a改成nonlocal a,代码这里我就不重复贴了,运行后查看结果,可以看到我们真的引用了outer函数的a变量。

函数outer调用之前全局变量a的内存地址:  497726528
函数outer调用之时闭包外部的变量a的内存地址:  497726560
函数inner调用之后闭包内部变量a的内存地址:  497726592
函数inner调用之后,闭包外部的变量a的内存地址:  497726592
函数outer执行完毕,全局变量a的内存地址:  497726528

面试真题

不要上机测试,请说出下面代码的运行结果:

a = 10
def test():
    a += 1
    print(a)
test()

很多同学会说,这太简单了!函数内部没有定义a,那么就去外部找,找到a=10,于是加1,打印11!

我会告诉你,这段代码有语法错误吗?a += 1相当于a = a + 1,按照赋值运算符的规则是先计算右边的a+1。但是,Python的规则是,如果在函数内部要修改一个变量,那么这个变量需要是内部变量,除非你用global声明了它是外部变量。很明显,我们没有在函数内部定义变量a,所以会弹出局部变量在未定义之前就引用的错误。

更多的例子

再来看一些例子(要注意其中的闭包,也就是函数内部封装了函数):

name = 'jack'

def outer():
    name='tom'

    def inner():
        name ='mary'
        print(name)

    inner()

outer()

上面的题目很简单,因为inner函数本身有name变量,所以打印结果是mary。那么下面这个呢?

name ='jack'

def f1():
    print(name)

def f2():
    name = 'eric'
    f1()

f2()

这题有点迷惑性,想了半天,应该是‘eric’吧,因为f2函数调用的时候,在内部又调用了f1函数,f1自己没有name变量,那么就往外找,发现f2定义了个name,于是就打印这个name。错了!!!结果是‘jack’!

Python函数的作用域取决于其函数代码块在整体代码中的位置,而不是调用时机的位置。调用f1的时候,会去f1函数的定义体查找,对于f1函数,它的外部是name ='jack',而不是name = 'eric'

再看下面的例子,f2函数返回了f1函数:

name = 'jack'

def f2():
    name = 'eric'
    return f1

def f1():
    print(name)

ret = f2()
ret()

仔细回想前面的例子,其实这里有异曲同工之妙,所以结果还是‘jack’。


 参数类型 range()函数 

评论总数: 11


点击登录后方可评论

也许这样讲不合适,但还是想表达出来: 网络上很多博主都撰写过各种各样的编程教程,首先为他们愿意分享的精神点赞! 但不得不说,像刘江老师这样把细节写透写深入的博主并不多; 尤其是一些看似与知识主干无关的废话,恰恰是许多初学者想理解的地方。 我自己有10年的开发经验,在看刘老师的教程时仍会发现不少细节自己理解得不准确, 这充分说明自己最开始学的时候没有用心。 这也让我反思中国的教育体系,学生时代很多老师都是灌输式教育, 甚至自己都没有对所教学科有深入的理解,因为他们缺乏骨子里的热爱。 刘老师属于那种兴趣导向然后精益求精的风格。 再次致敬和点赞!



很高兴我的教程能给你一点收获,但是这个评价太高了,真不敢当。只是出于对技术的热爱和对代码的追求,花了点时间研究了一下,形成了这部教程。俗话说,寸有所长尺有所短,每个人都会有自己触摸不到的细节。我在刚开始学习Python的时候,基本不放过任何一篇博文,哪怕它内部90%的东西我都了如指掌,因为有10%的可能存在我没有掌握到的点。并且重复的阅读并不是浪费时间,它能帮你加深记忆,让已知的更牢固,让未知的变成已知。当你掌握的细节足够多的时候,它们就会串起来形成思维导图,帮助你深入理解语言的精髓和设计原则。最后,教程肯定有不足或谬误之处,请多多指正。谢谢。



博主你好,最后的例子中的 ret( ) 处没看懂, 可以讲一下吗?谢谢



a = 1 print("函数outer调用之前全局变量a的内存地址: ", id(a)) def outer(): a = 2 print("函数outer调用之时闭包外部的变量a的内存地址: ", id(a)) def inner(): global a # 注意这行 a = 3 print("函数inner调用之后闭包内部变量a的内存地址: ", id(a)) inner() print("函数inner调用之后,闭包外部的变量a的内存地址: ", id(a)) outer() print("函数outer执行完毕,全局变量a的内存地址: ", id(a)) ---------------------分割线-------------------------------- 上面这段代码,我弄不明白的是:调用了inner()函数以后 , 明明在inner()里面声明过global a了 ,为什么接下来print(id(a))的时候,打印的还是a=2 的内存地址而不是a=3的内存地址呢?



在函数内要修改一个可变类型的全局变量,是可以修改的



确切的说,修改可变类型中的元素,其实不是修改这个类型本身。房子还是那个房子,只是内部家具变了。



理解了



a += 1相当于a = a + 1,按照赋值运算符的规则是先计算右边的a+1。但是,Python的规则是,如果在函数内部要修改一个变量,那么这个变量需要是内部变量。 ;;这个应该不是修改的本身吧,,+1应该就是运算才对吧,ID地址不会变,房子里的东西变了,,,这样理解对吗



长知识了



666.。。真的都是满满的精华!!



我会告诉你,这段代码有语法错误吗?a += 1相当于a = a + 1,按照赋值运算符的规则是先计算右边的a+1.但是,Python的规则是,如果在函数内部要修改一个变量,那么这个变量需要是内部变量,除非你用global声明了它是外部变量。很明显,我们没有在函数内部定义变量a,所以会弹出局部变量在未定义之前就引用的错误。 ---------------------分割线-------------------------- 这里,有些许的歧义. 问题不是出在 a+1这里 ,而是出现在了赋值这里. 其实下面已经说清楚了,但总觉得有一点别扭