python类中的局部变量_Python成为专业人士笔记--变量的范围和绑定深入剖析

专业人士笔记”系列目录:

创帆云:Python成为专业人士笔记--强烈建议收藏!每日持续更新!​zhuanlan.zhihu.com
c3b2a427348937903410b92e0f0523aa.png

nonlocal变量

Python 3添加了一个名为nonlocal的新关键字, nonlocal 关键字将使变量的声明覆盖到它的上级范围(如父、子函数关系)。你可以在PEP 3104中阅读有关它的所有内容。通过几个代码示例可以很好地说明这一点:

#这个counter函数会返回一个迭代器,每调用一次值+1

def counter():
    num = 0
    def incrementer():
        num += 1
        return num
     return incrementer

#调用函数
c=counter();
print(c())

如果您尝试运行这段代码,您将收到一个UnboundLocalError报错,因为num变量在最内层的函数(incrementer)中进行了赋值,但是对这个内层函数来说,它并不知道num变量已经在它的父函数(counter)中已经申明过了,如果我们加一个nonlocal关键词,如下:

 def counter():
     num = 0
     def incrementer():
        nonlocal num
         num += 1
         return num
     return incrementer

 c = counter()

 c() # 输出: 1
 c() # 输出: 2
 c() # 输出: 3

 #这里正常输出了,通过nonlocal关键词,申明num变量为整个counter函数内都可用

基本上,nonlocal 关键词将允许你在上级范围内分配变量,但不允许在全局范围内分配,所以你不能在我们的计数器(counter)函数中使用nonlocal,因为那样它会试图分配给一个全局范围(因为counter没有父函数了)

尝试一下,你将很快得到一个SyntaxError语法错误

相反,必须在嵌套函数中使用nonlocal,这一点尤其要注意!

全局变量

直接看一段代码:

 x = 'Hi' #函数外申明的,这个是全局变量

 def read_x():
     print(x)  
     # x 对全局变量进行了使用

 read_x()  # 打印出 Hi

 def read_y():
     print(y)
     # y 试图对全局变量进行了使用,但之前没有定义过y这个全局变量

read_y()  # 报错,因为y这个变量未定义

def read_y():
    y = 'Hey'
    #这里的 y 是定义的全局变量
    print(y)
    # 打印出 y 的值

read_y()   #输出: ‘Hey’

局部变量

如果在函数中绑定了一个变量名,则默认情况下只能在函数中访问该变量名:

 def foo():
     a = 5
     print(a) #如果调用函数,这里可以正常输出 
 
 print(a) 
 #在这里调用会直接报错,因为print这行代码是全局范围内的,其并不能访问foo函数内的变量a

del 命令

这个命令有几个相关但不同的形式

del v

如果v是一个变量,则命令del v将该变量从其作用域中删除,如下代码:

 x = 5
 print(x) # 输出: 5

 del x
 print(x) # NameError: name 'f' is not defined 变量名错误,变量f没有定义

请注意:del会触发变量在范围内删除变量的事件,这意味着除非显式声明(使用nonlocal或global关键字),否则del v将只对当前作用域的本地变量有效。如果要删除外部范围中的v,请在del v语句前加上nonlocal或global关键字

In all the following, the intention of a command is a default behavior but is not enforced by the language. A class
might be written in a way that invalidates this intention.

del v.name

此命令触发对v.__delattr__(name)的调用

其目的是使属性名不可用。例如 :

 class A:
     pass

 a = A()
 a.x = 7
 print(a.x) # 输出: 7

 del a.x
 print(a.x) # 报错: AttributeError: 'A' object has no attribute 'x',即提示A这个类里面已经没有x这个属性了

del v

此命令触发对v.__delitem__(item)的调用。

其目的是使item项不再属于对象 v:

 x = {'a': 1, 'b': 2}
 del x['a']
 print(x) # 输出: {'b': 2}

 print(x['a']) # 报错: KeyError: 'a' 即该项不存在

del v[a:b]

This actually calls v.__delslice__(a, b).

其目的与上面描述的类似,但使用的是范围,而不是单个项。例如

x = [0, 1, 2, 3, 4]

del x[1:3]

print(x) # 输出: [0, 3, 4] 将1和2删除了

更多可参见Garbage Collection# del命令。

函数在查找变量名时跳过类作用域

类在定义时有一个类的局部作用域,但是类中的函数在查找变量名时不使用该作用域,这可能会导致一些出乎意料外的行为 :

 a = 'global'
 class Fred:
    a = 'class'   #将a在类里重新赋值,但注意这只是类 class 的作用域
    b = (a for i in range(10))  #这是函数作用域,里面的a引用的是全局变量而不是类的变量,尤其要注意
    d = a # 类作用域
    e = lambda: a # 函数作用域 lambda是匿名函数

  @staticmethod
  或@classmethod,或其他常规实例方法 
  def g(): # 函数作用域
      return a

 print(Fred.a)
 #输出:class
 print(next(Fred.b))
 #输出:global
 print(Fred.d)
 #输出:class
 print(Fred.e())
 #输出:global

 print(Fred.g()) 
 #输出: global

如果仍然不太理解这个范围如何工作,你可以直接把b和e整个打印出来试试

在类块中定义的变量名的范围仅限于类这个作用域;它不会扩展到函数里的代码块,这包括表达式生成器(如lambda这样的匿名函数或for表达式,因为它们是使用函数作用域实现的。这意味着下面的操作将失败 :

class A:
    a = 42
    b = list(a + i for i in range(10))

局部变量 与 全局变量 比较

什么是局部和全局作用域?

在代码中可以访问的所有Python变量都在局部作用域或全局作用域中,这里的解释是,局部作用域包括当前函数中定义的所有变量,全局作用域则还包括当前函数外部定义的变量。

foo = 1  #全局变量
def func():
    bar = 2 #局部变量

 print(foo)
 #从全局作用域打印变量

 print(bar)
# 从局部作用域打印变量

可以检查哪些变量在哪个范围内,内置locals()和globals()以字典的形式返回整个作用域。

foo = 1

def func():
    bar = 2
    print('局部变量',locals())
    print('全局变量', globals())

#调用函数输出
func()

#输出内容:
局部变量 {'bar': 2}
全局变量 {'name': 'main', 'doc': None, 'package': None, 'loader': <_frozen_importlib_external.SourceFileLoader object at 0x7f920cc05710>, 'spec': None, 'annotations': {}, 'builtins': , 'file': '/home/python-study/main.py', 'cached': None, 'foo': 1, 'func': }

如果变量名冲突了会发生什么呢?

 foo = 1
 def func():
     foo = 2
     # 在局部范围内创建一个新的变量foo,全局foo不受影响 
     print(foo)
     #输出 2
     #这时全局变量foo仍然存在,并且值没有改变 
     print(globals()['foo'])
     #输出 1
     print(locals()['foo'])
     #输出 2 #调用的局部变量

#调用函数看结果会发生什么,和上面看到的应该一样
func()

若要修改全局变量,请使用关键字global :

 foo = 1
 def func():
     global foo
     foo = 2
     #这将修改全局foo,而不是创建一个局部变量 

记住,默认函数体内定义的变量,是局部变量,而不会影响全局变量,即使变量名是同一个!

即一个变量永远不会在函数中一半是全局的,一半是局部的,反之亦然。

 foo = 1
 def func():
 #这个函数有一个局部变量foo,因为它是在下面代码中定义的。foo在函数内是局部的,全局foo会被隐藏。 
     print(foo) #  引发UnboundLocalError,因为局部foo尚未初始化 
     foo = 7
     print(foo) #正常输出变量值 7

     print(globals()['foo']) #正常输出全局变量值 1

嵌套函数中:

函数中可能嵌套了许多层函数,但是在任何一个函数中,该函数只有一个局部作用域和全局作用域,没有两者同时存在的情况

 foo = 1
 def f1():
     bar = 1
     def f2():
         baz = 2
         #在这里,foo是一个全局变量,baz是一个局部变量 
         #print(locals().keys())
         #输出:['baz']
         print('bar' in locals())
         #输出:False
         print('bar' in globals())
         #输出:False

     def f3():
         baz = 3 
         #print(bar)
         #引用f1中的bar,使其进入f3的局部范围(闭包) 
         #print(locals().keys())
         #输出['bar', 'baz']
         print('bar' in locals())
         #输出:True
         print('bar' in globals())
         #输出:False

        def f4():
            bar = 4 
            #一个新的局部变量 bar ,f1中的bar变量在这个范围内不可用
            baz = 4
            print(bar)
            print(locals().keys())
            #输出:['bar', 'baz']
            print('bar' in locals())
            #输出:True
            print('bar' in globals())
            #输出:False

global关键字 和 nonlocal关键字比较

这两个关键字都用于获得对非当前函数本地变量的写访问权

用global关键字声明变量名后,应该将该变量视为全局变量

 foo = 0
 #全局变量 foo
 def f1():
     foo = 1
     #f1函数中,申明的一个新的局部变量foo
 
     def f2():
         foo = 2
         #一个新的局部变量foo,其作用域在函数f2中
         def f3():
             foo = 3
             #一个新的局部变量foo,其作用域在函数f3中 
             print(foo)
             #输出值:3
             foo = 30
             #只修改了f3中的局部变量foo,其他的同名变量未受到影响

             def f4():
                 global foo #注意:这里用global关键字申请了全局变量使用权
                 print(foo)  
                 #输出:0  明显这个是全局变量的初始值
                 foo = 100
                 #这里修改的是,全局变量的值

另一方面,Python 3中提供的nonlocal变量(请参阅之前写的nonlocal变量章节)将一个局部变量从封闭的范围内提升到上一级函数作用域范围:

 def f1():
     def f2():
         foo = 2
         #在f2中申明了一个新的局部变量foo
         def f3():
             nonlocal foo # 用nonlocal变量申明了,从上一级函数(f2)中拿取该变量foo
            print(foo)
            #输出:2 这里引用了f2中的变量
            foo = 20
            #修改了f2中的变量foo的值!这里尤其要注意!

绑定事件

 x = 5
 x += 7
 for x in iterable: pass

上面的每个语句都是绑定事件——x被绑定到用5表示的对象。如果该语句出现在函数中,则x默认绑定到本地函数;有关绑定语句列表,请参阅python官方文档中“语法”部分。

今天的分享就到这里,禁止转载,违者必究!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值