Python 变量作用域 LEGB

回顾 - Decorator

前篇有讲到了, 闭包和装饰器的概念. 闭包就是, 函数内部嵌套函数. 而 装饰器只是闭包的特殊场景而已, 特殊在如果外函数的参数是指向一个, 用来被装饰的函数地址时(不一定是地址哈, 随意就好) , 就有了 "@xxx" 这样的写法, 还是蛮有意思的. 装饰器的作用是 在不改变原函数的代码前提下, 额外给原函数填写新功能. 写法上来看, 还是比较简洁优雅的.

装饰器的通俗写法

# 装饰器的通用写法
def out(func):
    def inner(*args, **kwargs):
        print("we are checking...", args[0])
        return func(*args, **kwargs)

    return inner

@out
def check_2019_nCov(name):
    return f"now, {name} is very healthy..."

tmp = check_2019_nCov('youge')
print(tmp)

# output
we are checking... youge
now, youge is very healthy...

给装饰器传参

虽然这种 "@" 的写法, 是要求 外函数的参数是一个 func 地址 , 但要达到可以传参, 只要 再在外面包一层函数 (作用是接受参数) , 这样不就相当于扩大作用空间, 拿到参数了呀 .

# 最外层的函数作用是, 给装饰器传递参数
def get_param(*args, **kwargs):
    def out(func):
        def inner(*args, **kwargs):
            print("get params", args, kwargs)
            return func(*args, **kwargs)

        return inner

    return out

@get_param("youge")
def check_2019_nCov(name):
    return f"now, {name} is very healthy..."

tmp = check_2019_nCov("youge")
print(tmp)

# output
get params ('youge',) {}
now, youge is very healthy...

这种个装饰器传递参数的应用场景, 在 Web应用中, 以 Flask 为例, 就是所有的 路由 url 的概念呀, 如 route("/login") 这样的写法, 其原理就是用各种装饰器来实现 路由 -> 视图 的映射关系的.

仔细一看, 整个过程忽略了一个重要的话题, 即命名空间, 及 变量的作用域, 或者说命名空间如怎样的.

LEGB 法则

命名空间

前篇已经详细阐述过了, Python 变量的本质是指针, 是对象的引用, 而 Python中 万物皆对象. 这个对象是真正存储数据的内存地址, 是各种类(数据类型, 数据结构) 的实例. (变量就是用来引用对象的) 差不多这个意思吧.

最为直观的解释:

" A namespace is a mapping from names to objects". (变量名和对象的映射)

"Most namespaces are currently implemented as Python dictionaries." (大部分命名空间通过字典来实现)

即命名空间是用来 避免变量命名冲突 的约束. 各个命名空间是彼此独立的, 一个空间中不能重名, 不同空间中是不没有关系的. 就跟 计算机系统, 存储文件是样的逻辑.

for i in range(10):
    print(i)

# 这两句话都用到了 i 但其各自的空间是不一样的.

[i for i in range(100)]
  • 内置空间: (built-in names): Python 内置名称, 如内置函数,异常类...
  • 全局空间: (global names): 常量, 模块中定义的名称(类, 导入模块)...
  • Enclosed: 可能嵌套在函数内的函数等...
  • 局部名称: (local names): 函数中定义的名称(函数内的变量) ...

Python 查找变量顺序为:Local -> Enclosed -> Global -> Built-in

其实, 从我个人经验而言, 能区分 局部和全局 的 相对性. 就好了, 基本上. 直观上, 以一个写代码的 py文件为例. 最外层有, 变量, 类定义, 函数定义, 从from .. import .. 的变量或函数名, 这些就是 全局变量, 最外面的类或者函数, 里面是各自的名字空间呀.

# var1 是 global
var1 = 666

def foo():
    # var2 是局部
    var2 = 666
    def foo2():
        # 内嵌的局部
        var3 = 666

        # print(var2)

print(var3)  # G->L 是找不到的哦
# 在 foo2 中 寻找 var2 是 L->E 是ok的
# 在 foo  中 寻找 var2 是 E->L 是不行的

其实很好理解的. 就上段code来说,根据 L-E-G-B 法则, 其实理解一个 相对 就可以了.

全局 vs 局部

total = 0  # 全局

def sum(a, b):
    """重写内置sum"""
    total = a + b
    print("局部total:", total)

sum(1, 1)
print("全局total:", total)

# output
局部total: 2
全局total: 0

可以看到, 局部是不会改变全局的哦, 而在局部内是可以拿到全局变量的. 不然闭包, 外函数接收的参数, 内函数怎么可以拿到呢? 就是外函数, "扩充了" 内函数的作用域呀, 根据 L->E->G->B 法则去搜索到.

global 和 nonlocal

name = "youge"

def change_name():
    name = "youyou"

# 希望将 "youge" 改为 "youyou"
change_name()
print(name)

# output
youge

发现没有能改掉, 这是自然的. 因为, 在调用函数时, 里面的 name 是一个 Local 变量, 是不会影响到全局的 name的, 如果想实现在 在函数内部来改变 全局变量, 则将 该变量用 global 关键字声明即可.

name = "youge"

def change_name():

    global name
    name = "youyou"

# 希望将 "youge" 改为 "youyou"
change_name()
print(name)

# output
youyou

很简单, 在函数内部, 用 global 将其声明为全局变量即可. 同样, 针对于** 函数嵌套, 即向闭包, 装饰器等, 通过 关键字 nonlocal 实现将 函数内的变量, 声明为 函数外的 Enclose 层**

name = "jack"

def outer():
    name = "youge"

    # 函数内有一个local函数空间
    def inner():
        name = "youyou"
        print("local:", name)

    inner()  # 尝试改掉 嵌套层的 name
    print("encolse:", name)

print("global:", name)

outer()

# output
global: jack
local: youyou
encolse: youge

现在想在, inner函数 (L层) 中来修改 E 层的 name, 即在inner中将 name 声明为 nonlocal 即可.

name = "jack"

def outer():
    name = "youge"

    # 函数内有一个local函数空间
    def inner():
        nonlocal name
        name = "youyou"
        print("local:", name)

    inner()  # 尝试改掉 嵌套层的 name
    print("encolse:", name)

print("global:", name)

outer()

# output
global: jack
local: youyou
encolse: youyou

函数嵌套场景中, 通过 在 local 层, 声明 name 为 nonlocal 则将 enclosed 层的name改掉了. 但如果在 local 层 声明 global 则是没有其效果的, 为啥, 嗯... 暂时还不清楚, 也是实验的, 暂时.

哦, 突然想贴一个, 我还是菜鸟时常, 犯的小错误:

name = 'youge'
def change_name():
    name = name +  "youyou"

change_name()
print(name)

# output
UnboundLocalError: local variable 'name' referenced before assignment

原因就在于, 在函数内部的空间中, 对 name 是没有定义的. 在 Python中, 对于函数过程的存储, 是通过 递归栈 实现的. 利用栈的 FILO, (先进后出) 的特点, 当遇到一个函数, 就用栈将其参与的成员, 依次入栈, 如有 return 则将置为栈元素.

变量要先定义, 后使用嘛, Python中的定义是指, 该变量指向某个实例对象即可, 而非 其它语言中的 类型声明 哦, 这里最容易混淆.

修改 name 为全局变量,通过函数参数传递即可:


# 方式1: 定义个单独的函数来处理
name = 'youge'

def change_name(s):
    name = s +  "youyou"
    print(name)

# 全局变量来传递给 函数空间, 即"先定义, 后执行")

change_name(name)    

# output
yougeyouyou
# 方式2: 声明为全局即可, 不推荐
name = 'youge'

def change_name():
    global name
    name = name + "youyou"

change_name()
print(name)

# output
yougeyouyou

小结

  • 闭包, 装饰器的本质是函数的嵌套, 参数及函数能被传递的原因是, Pyhton变量的本质是之指针
  • Python中用 命名空间 来 解决 变量名冲突, 原理跟 计算机系统(如 Linux) 存储文件是一样的逻辑
  • 变量名寻找的规则为 Local -> Enclosed -> Global -> Built-in
  • 个人觉得能理解,全局与局部的"相对性" 即可, 另外, 可用 global 与 nonlocal (E层) 改变变量作用等级.

变量作用域, 一直在用, 但却经常忽略它, 这里做个总结, 没事常翻翻, 作用域, 就到这吧.

原文地址:https://www.cnblogs.com/chenjieyouge/p/12243544.html

时间: 2024-07-29 04:26:25

Python 变量作用域 LEGB的相关文章

Python 变量作用域 LEGB (下)—— Enclosing function locals

上篇:Python 变量作用域 LEGB (上)—— Local,Global,Builtin https://www.cnblogs.com/yvivid/p/python_LEGB_1.html 下篇 没想到 拖这么久,距离上篇完成 都一年多了. 一.闭包常规形态下的 locals作用域  典型的闭包 如下: def outer(x = 3): def inner(y): print("yvivid's test") print("Locals =", loca

Python变量作用域

Python对于作用域有四种:buildin作用域.全局作用域(模块作用域或文件作用域).高层函数作用域和局部作用域. 其中buildin作用域是Python内建作用域,在Python初始化时建立的: 全局作用域是在文件中直接定义的变量所处的作用域: 高层函数作用域是外层函数中定义的局部变量所处的作用域: 局部作用域是函数内部定义的局部变量所处的作用域: 而在Python语言中变量赋值的位置即是其变量所处的作用域. Python在查找一个变量时首先从局部作用域查起,若未找到则依次查找高层函数作用

python 变量作用域

python能够改变变量作用域的代码段是def(函数).class(类).lamda. if/elif/else.try/except/finally.for/while 并不能涉及变量作用域的更改,也就是说他们的代码块中的变量,在外部也是可以访问的,这点与有{}标注界限的其他类型语言不通.特别注意. 变量搜索路径是:本地变量->全局变量

Python变量作用域(一)

在一个程序中使用变量名时,Python创建.改变或者查找变量名都是在所谓的命名空间中进行的.作用域指的就是命名空间. Python中的变量名在第一次赋值时已经创建,并且必须经过赋值后才能够使用.由于变量名最初没有声明,Python将一个变量名 被赋值的地点关联为一个特定的命名空间.也即是说,在代码中给一个变量赋值的地方决定了这个变量将存在于哪个命名空间,也 就是它可见的范围. 函数除了打包代码之外,还为程序增加了一个额外的命名空间曾:在默认的情况下,一个函数的所有变量名都是与函数的命名空间 相关

39 py函数作用域递归函数 变量作用域局部函数 使用lambda

第十课:函数作用域 // python 中的嵌套函数 在一个函数中再定义一个函数 # 小结 : # 函数作用域:因为:python是动态语言,定义变量的时候是不需要指定变量类型的,这样的话,我们在使用或者定义变量的时候作用域会分不清 # 如果在函数中定义一个变量,而且变量名和该函数上一级的作用域中的变量名相同 # 那么在该函数使用该变量时,就会使用局部变量 # 如果在函数中使用一个变量,但该变量在函数中并没有定义,那么会到该函数上一层的作用域去寻找该变量,如果还没有找到,会继续到上一层作用域去寻

[Python] 命名空间&作用域

Python的类语句不会创建实例 类会创建命名空间,通过对象访问类的属性和方法 类不会创建作用域,对方法和属性的引用必须加以限定(如在方法中必须通过self引用实例的属性) class My1(): my1 = "My1bianliang" def __init__(self): print("My1gouzao") def __del__(self): print("My1xigou") class My2(): def __init__(se

python变量和作用域

1.作用域介绍 python中的作用域分4种情况: L:local,局部作用域,即函数中定义的变量: E:enclosing,嵌套的父级函数的局部作用域,即包含此函数的上级函数的局部作用域,但不是全局的: G:globa,全局变量,就是模块级别定义的变量: B:built-in,系统固定模块里面的变量,比如int, bytearray等. 搜索变量的优先级顺序依次是:作用域局部>外层作用域>当前模块中的全局>python内置作用域,也就是LEGB. x = int(2.9) # int

2015/9/19 Python基础(15):变量作用域及生成器

变量作用域标识符的作用域是定义为其声明的可应用范围,或者即是我们所说的变量可见性.也就是,我们可以在程序的那个部分去访问一个制定的标识符.全局变量与局部变量定义在函数内的变量有局部作用域,在一个模块中最高级别的变量有全局作用域.全局变量的一个特征是除非被删除掉,否则它们将存活到脚本运行结束,且对于所有的函数,他们的值都是可以被访问的,然而局部变量,就像它们存放的栈,暂时地存在,仅仅只依赖于定义它们的函数现阶段是否处于活动.当一个函数调用出现时,其局部变量就进入声明它们的作用域.在那一刻,一个新的

2015/9/19 Python基础(14):变量作用域及生成器

变量作用域标识符的作用域是定义为其声明的可应用范围,或者即是我们所说的变量可见性.也就是,我们可以在程序的那个部分去访问一个制定的标识符.全局变量与局部变量定义在函数内的变量有局部作用域,在一个模块中最高级别的变量有全局作用域.全局变量的一个特征是除非被删除掉,否则它们将存活到脚本运行结束,且对于所有的函数,他们的值都是可以被访问的,然而局部变量,就像它们存放的栈,暂时地存在,仅仅只依赖于定义它们的函数现阶段是否处于活动.当一个函数调用出现时,其局部变量就进入声明它们的作用域.在那一刻,一个新的