Python变量作用域LEGB用法解析


Posted in Python onFebruary 04, 2020

这篇文章主要介绍了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层) 改变变量作用等级.
  • 变量作用域, 一直在用, 但却经常忽略它, 这里做个总结, 没事常翻翻, 作用域, 就到这吧.

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Python 相关文章推荐
Python基于select实现的socket服务器
Apr 13 Python
深入解析Python编程中super关键字的用法
Jun 24 Python
Python实现压缩和解压缩ZIP文件的方法分析
Sep 28 Python
python中hashlib模块用法示例
Oct 30 Python
Python编程scoketServer实现多线程同步实例代码
Jan 29 Python
Python中defaultdict与lambda表达式用法实例小结
Apr 09 Python
Tensorflow 查看变量的值方法
Jun 14 Python
Python学习笔记之读取文件、OS模块、异常处理、with as语法示例
Jun 04 Python
python异步实现定时任务和周期任务的方法
Jun 29 Python
python求最大公约数和最小公倍数的简单方法
Feb 13 Python
pycharm 2018 激活码及破解补丁激活方式
Sep 21 Python
keras 回调函数Callbacks 断点ModelCheckpoint教程
Jun 18 Python
如何在python开发工具PyCharm中搭建QtPy环境(教程详解)
Feb 04 #Python
TensorFlow基本的常量、变量和运算操作详解
Feb 03 #Python
Tensorflow轻松实现XOR运算的方式
Feb 03 #Python
Tensorflow不支持AVX2指令集的解决方法
Feb 03 #Python
基于Python3.6中的OpenCV实现图片色彩空间的转换
Feb 03 #Python
解决Tensorflow 使用时cpu编译不支持警告的问题
Feb 03 #Python
tensorflow2.0保存和恢复模型3种方法
Feb 03 #Python
You might like
php file_put_contents()功能函数(集成了fopen、fwrite、fclose)
2011/05/24 PHP
PHP随机数 C扩展随机数
2016/05/04 PHP
基于jQuery的计算文本框字数的代码
2012/06/06 Javascript
关于JS控制代码暂停的实现方法分享
2012/10/11 Javascript
JavaScript实现的圆形浮动标签云效果实例
2015/08/06 Javascript
详解JS-- 浮点数运算处理
2016/11/28 Javascript
JS实现线性表的链式表示方法示例【经典数据结构】
2017/04/11 Javascript
详解Vue 普通对象数据更新与 file 对象数据更新
2017/04/26 Javascript
Angular 2.0+ 的数据绑定的实现示例
2017/08/09 Javascript
使用命令行工具npm新创建一个vue项目的方法
2017/12/27 Javascript
纯JS实现五子棋游戏
2020/05/28 Javascript
[02:42]DOTA2城市挑战赛收官在即 四强之争风起云涌
2018/06/05 DOTA
Python中的Numpy入门教程
2014/04/26 Python
Python操作MySQL简单实现方法
2015/01/26 Python
python中zip和unzip数据的方法
2015/05/27 Python
Python复制文件操作实例详解
2015/11/10 Python
django 开发忘记密码通过邮箱找回功能示例
2018/04/17 Python
Python实现的拟合二元一次函数功能示例【基于scipy模块】
2018/05/15 Python
python计算两个矩形框重合百分比的实例
2018/11/07 Python
详解Python 4.0 预计推出的新功能
2019/07/26 Python
Python reshape的用法及多个二维数组合并为三维数组的实例
2020/02/07 Python
Django Channel实时推送与聊天的示例代码
2020/04/30 Python
tensorflow与numpy的版本兼容性问题的解决
2021/01/08 Python
CSS3制作气泡对话框的实例教程
2016/05/10 HTML / CSS
HTML5本地存储之Database Storage应用介绍
2013/01/06 HTML / CSS
俄罗斯极限运动网上商店:Board Shop №1
2020/12/18 全球购物
企划主管岗位职责
2013/12/12 职场文书
法学专业本科生自荐信范文
2013/12/17 职场文书
人力资源主管的岗位职责
2014/03/15 职场文书
考核工作实施方案
2014/03/30 职场文书
天堂的孩子观后感
2015/06/11 职场文书
采购部2015年度工作总结
2015/07/24 职场文书
导游词之青岛太清宫
2019/12/13 职场文书
OpenCV图像变换之傅里叶变换的一些应用
2021/07/26 Python
Mysql超详细讲解死锁问题的理解
2022/04/01 MySQL
instantclient客户端 连接oracle数据库
2022/04/26 Oracle