Python探讨--变量-作用域

Python探讨–变量-作用域

1. 作用域

对于有编程基础的伙伴们来说,作用域是很熟悉的名字了,这里我们仅作简单解释。

大家都知道,在Python中一切皆对象,那么一个对象就好比一个人:在历史长河中,人有生老病死,在软件运行周期中,一个变量有创建,使用,回收(删除);在人类社会中,人有不同的影响范围(智定天下的张良和隔壁村里掏鸟窝的张良不是同一个),在软件程序中,变量有不同的作用域(全局的变量obj和局部的obj不是同一个)

obj = "全局变量"
def function():
    obj = "局部变量"
    print("obj 是:%s" % obj)  

function()
print("obj 是:%s" % obj)

obj 是:局部变量
obj 是:全局变量

作用域:程序代码中,一个变量不是任何地方有效的,而该变量的有效空间范围就是该变量的作用域。简言之,作用域指该变量可被访问(查找)的最大区域。

如下所示,函数内定义的变量obj_l仅在函数内有效,其作用域为函数function包含的区域。

obj_g = "全局变量"
def function():
    obj_l = "局部变量"

function()
print("全局是否存在变量 obj_g: %s" % ("obj_g" in locals()))
print("全局是否存在变量 obj_l: %s" % ("obj_l" in locals()))

全局是否存在变量 obj_g: True
全局是否存在变量 obj_l: False
  • locals :内置方法,返回值是字典格式,返回当前命名空间内的变量

2. 命名空间

2.1 定义

什么是命名空间?或者说,当程序运行时我们需要访问变量(假设变量名为 obj ),那么解释器如何找到该变量呢?

程序运行时,对于每一个函数、类、模块(乃至于全局的builtins)都会开辟一块用于存储当前区域内声明的变量-值的内存空间,这就是命名空间。

简言之,每个函数、类都对应一个命名空间,该空间内存放着一张表,表里记录了函数、类中定义的变量及其内容。

def function():
    obj_l = "局部变量"
    print("局部命名空间: %s" % locals())

function()
print("模块命名空间: %s" % locals())

局部命名空间: {'obj_l': '局部变量'}
模块命名空间: {
    '__file__': '/home/admin/python/test.py', 
    '__builtins__': <module 'builtins' (built-in)>, 
    '__name__': '__main__', 
    'obj_g': '全局变量'}
    ...
}
  • python中命名空间采用字典格式存储数据。
  • 解释器在命名空间内查找变量。
  • python是解释性语言,从上往下执行时不断向命名空间内添加内容。

2.2 划分

2.2.1 Local

局部命名空间(local)指在一个函数或一个类中定义的变量或引用的字典集合,使用locals()即可获得局部的命名空间,返回一个字典。

2.2.2 Enclosed

闭包中的命名空间,用于存储外层的局部变量。

def function():
    obj_l = "enclosed 变量"
    def wrapper():
        obj_l = "local 变量"
        print("local命名空间: %s" % locals())

    print("enclosed命名空间: %s" % locals())
    wrapper()

function()

enclosed命名空间: {'obj_l': 'enclosed 变量', 'wrapper': <function function.<locals>.wrapper at 0x7fb0b6462ea0>}
local命名空间: {'obj_l': 'local 变量'}
  • 可以看出,wrapper方法执行时,解释器优先使用局部命名空间内的变量。这个顺序后面会讲到。
2.2.3 Global

模块命名空间(Global)即在当前模块中的所有的对象的字典集合,使用globals()获取模块命名空间。

from datetime import datetime

def function():
    obj_l = "局部变量"
    print("局部命名空间: %s" % locals())

function()
print("模块命名空间: %s" % locals()) #等价于globals

局部命名空间: {'obj_l': '局部变量'}
模块命名空间: {
    '__file__': '/home/admin/python/test.py', 		
    '__builtins__': <module 'builtins' (built-in)>, 
    '__name__': '__main__', 
    '__package__': None, 
    'obj_g': '全局变量'},
    'datetime': <class 'datetime.datetime'>,
        ...
}
  • 导入的模块内容也会添加进去
  • 在全局范围下使用locals方法与globals方法效果相同,即局部命名空间的“局部”是相对的,指当前语句所处位置的命名空间。
2.2.4 builtin

全局命名空间(builtin),是python的全局命名空间(囊括了当前Python环境中所有导入的内容)。

思考一个问题,为什么我们没有定义dict、list等类型,locals、globals等方法,却可以使用它。结合之前所学,解释器一定在某个命名空间内能够找到他,但之前三个命名空间内都没有找到这部分内容。

全局命名空间(builtin):是python全局的命名空间(囊括了当前Python环境中所有导入的内容),所有的内置类型、方法、变量都在里面进行存储。

之前使用globals方法查看时,返回了一个内容'__builtins__': <module 'builtins' (built-in)>。每个python模块都会有这样一个变量,用于指向自己的全局命名空间。


2.3 LREGB

2.3.1 简单分析

之前我们了解到,每个层次(方法,类,全局,模块等)都对应一个命名空间,每个命名空间(形如沙盒)相互独立,那么解释器在寻找变量时,要如何依次访问这些命名空间呢?

obj_g = "全局变量"

def function():
    obj_l = "enclosed 变量"
    def wrapper():
        obj_l = "local 变量"
        print("内层函数访问obj_l: %s" % obj_l)

    wrapper()
    print("外层函数访问obj_g: %s" % obj_g)

function()

内层函数访问obj_l: local 变量
外层函数访问obj_g: 全局变量

下面简单分析

  1. 内层函数访问obj_l:wrapper局部命名空间和闭包的enclosed命名空间内均有变量obj_l,解释器采用了局部的。
  2. 外层函数访问obj_g:enclosed命名空间内没有变量obj_g,解释器采用了全局的。
2.3.2 洋葱模型

简单来说,解释器查询变量时对命名空间的访问如下:

在这里插入图片描述

解释器从当前命名空间开始找,如果找到就使用(上例中local的覆盖了enclosed的),解释器如果没找到,则从内而外依次寻找,若builtins都未找到,抛出异常(NameError)。这便是python的LEGB规则,我喜欢用洋葱模型取描述,类似的情形以后会大量碰到。

3. 跨空间使用

3.1 简介

由pythonLEGB规则得知,我们可以访问上层命名空间内的变量,那么是否可以修改呢?

obj = "全局变量"
mutable = [1, 2]
def function():
    mutable[0] += 1  #通过
    obj += "!"		#异常


function()
  • 对于不可变类型,python不允许低层修改,只允许访问;
  • 对于可变类型,python允许低层修改,允许访问

3.2 跨域修改

那么,对于不可变类型真的无法修改了吗?

obj = "全局变量"
mutable = ["全局变量"]
def function():
    en_obj = "外层变量"
    def wrapper():
        mutable[0] += "!"
        nonlocal en_obj
        en_obj += "!"
        global obj
        obj += "!"
    wrapper()
    print(en_obj)

function()
print(obj)
print(mutable)

外层变量!
全局变量!
['全局变量!']
3.2.1 化不变为可变

参考mutable,将不可变(字符串)转化为可变的列表(封装)。底层就可以去修改内容,即python对底层的限制修改仅局限于变量本身是否为不可修改类型。

3.2.2 关键字
  1. global

变量名前加global,表示声明该变量在全局命名空间内。解释器会直接去全局中访问,相当于声明了一个全局变量。

  1. nonlocal

变量名前加nonlocal,表示声明该变量在上一层命名空间内。解释器会去上层命名空间中访问,注意的是,上一层不得是全局,通常时闭包中使用.

4. 笔录

  1. 不要在局部中修改上层变量,容易出事。推荐使用将结果return的方式,在调用端接受即可。
def function(obj):
    obj += "!"
    return obj

obj = "test"
obj1 = function(obj)

同样得到结果,同时避免出错

  1. for循环

python的for循环同c++, java的for循环不同,python的for循环更像js的foreach循环,虽然速度更快,但导致更多陷阱的出现。其中包括,对外部空间的污染。

for i in range(4):
    if i == 1:
        print(locals())
        break
        
print(locals())

{
    '__name__': '__main__', 
    '__builtins__': <module 'builtins' (built-in)>,
    'i': 1
    ...
}
{
    '__name__': '__main__', 
 	'__builtins__': <module 'builtins' (built-in)>, 
    'i': 1
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值