Python变量作用域LEGB用法解析

作者:小陈同学的数据之路 时间:2022-12-05 19:18:22 

这篇文章主要介绍了Python变量作用域LEGB用法解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下

闭包就是, 函数内部嵌套函数. 而 装饰器只是闭包的特殊场景而已, 特殊在如果外函数的参数是指向一个, 用来被装饰的函数地址时(不一定是地址哈, 随意就好) , 就有了 "@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

标签:Python,变量,作用域,LEGB
0
投稿

猜你喜欢

  • Python pandas.DataFrame调整列顺序及修改index名的方法

    2023-11-22 20:53:59
  • 详解vue3中组件的非兼容变更

    2024-04-28 09:23:04
  • Python文本终端GUI框架示例详细讲解

    2021-05-19 22:47:12
  • 原生js实现移动小球(碰撞检测)

    2024-04-19 09:56:01
  • python迷宫问题深度优先遍历实例

    2023-10-16 18:54:06
  • MySQL主从复制与读写分离原理及用法详解

    2024-01-14 21:16:25
  • python删除csv文件的行列

    2021-04-10 20:41:08
  • Python之re模块案例详解

    2022-07-19 11:06:59
  • Python中的字符串切片(截取字符串)的详解

    2023-07-23 20:37:59
  • Python的CGIHTTPServer交互实现详解

    2021-04-19 08:39:09
  • 举例详解Go语言中os库的常用函数用法

    2024-05-08 10:43:53
  • python中用Scrapy实现定时爬虫的实例讲解

    2023-07-26 05:47:01
  • Python通过4种方式实现进程数据通信

    2023-11-04 15:13:48
  • python获取引用对象的个数方式

    2023-06-19 23:10:30
  • Python中turtle库常用代码汇总

    2021-02-24 06:50:38
  • Django-silk性能测试工具安装及使用解析

    2023-12-08 08:53:19
  • Golang学习之无类型常量详解

    2024-02-14 13:41:09
  • vue如何实现动态加载脚本

    2024-05-25 15:19:28
  • Python编程之列表操作实例详解【创建、使用、更新、删除】

    2021-12-24 19:03:03
  • JS+CSS实现仿雅虎另类滑动门切换效果

    2024-04-17 10:34:55
  • asp之家 网络编程 m.aspxhome.com