我在偷偷学Python的第十九天(变量作用域)

本文深入探讨了Python中的变量作用域,包括局部作用域、全局作用域、闭包以及如何通过`global`和`nonlocal`关键字进行跨作用域访问。通过实例解析了变量查找规则,展示了`L-E-G-B`的搜索顺序,并提供了相关练习题帮助理解。

变量作用域

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

通常而言,在编程语言中,变量的作用域从代码结构形式来看,有块级、函数、类、模块、包等由小到大的级别。但是在Python中,没有块级作用域,

也就是类似if语句块、for语句块、with上下文管理器等等是不存在作用域概念的,他们等同于普通的语句。


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

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

  • L (Local) 局部作用域
  • E (Enclosing) 闭包函数外的函数中
  • G (Global) 全局作用域
  • B (Built-in) 内建作用域
 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))

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))

我们再来看下面的例子:

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的内存地址:  4313327920
函数outer调用之时闭包外部的变量a的内存地址:  4313327952
函数inner调用之后闭包内部变量a的内存地址:  4313327984
函数inner调用之后,闭包外部的变量a的内存地址:4313327952
函数outer执行完毕,全局变量a的内存地址:  4313327920

那么,如果,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的内存地址:  4360702256
函数outer调用之时闭包外部的变量a的内存地址:  4360702288
函数inner调用之后闭包内部变量a的内存地址:  4360702320
函数inner调用之后,闭包外部的变量a的内存地址:  4360702288
函数outer执行完毕,全局变量a的内存地址:  4360702320

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

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

~~----------------------分割线----------------------

以下是练习题:

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

1

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

2

name = 'jack'

def outer():
    name='tom'

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

    inner()

outer()

3

name ='jack'

def f1():
    print(name)

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

f2()

4

name = 'jack'

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

def f1():
    print(name)

ret = f2()
ret()

第一题答案

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


第二题答案:‘mary’
第三题答案:‘jack’
第四题答案:‘jack’

时间:2020/12/8
作者:Eamon

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值