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交换变量
Sep 06 Python
python获取文件版本信息、公司名和产品名的方法
Oct 05 Python
python实现从网络下载文件并获得文件大小及类型的方法
Apr 28 Python
Python的Django框架中forms表单类的使用方法详解
Jun 21 Python
详解python中字典的循环遍历的两种方式
Feb 07 Python
Python中模块string.py详解
Mar 12 Python
浅谈Python中的作用域规则和闭包
Mar 20 Python
解决Python 命令行执行脚本时,提示导入的包找不到的问题
Jan 19 Python
keras 如何保存最佳的训练模型
May 25 Python
Python如何对齐字符串
Jul 30 Python
Python 解析xml文件的示例
Sep 29 Python
pandas 实现将NaN转换为None
May 14 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获取服务器信息的实现代码
2013/02/04 PHP
PHP 5.3新增魔术方法__invoke概述
2014/07/23 PHP
PHP+jQuery实现即点即改功能示例
2019/02/21 PHP
PHP实现提取多维数组指定一列的方法总结
2019/12/04 PHP
js直接编辑当前cookie的脚本
2008/09/14 Javascript
jQuery 技巧大全(新手入门篇)
2009/05/12 Javascript
js 学习笔记(三)
2009/12/29 Javascript
suggestion开发小结以及对键盘事件的总结(针对中文输入法状态)
2011/12/20 Javascript
jquery复选框多选赋值给文本框的方法
2015/01/27 Javascript
jquery实现点击弹出带标题栏的弹出层(从右上角飞入)效果
2015/09/19 Javascript
JavaScript中数组去除重复的三种方法
2016/04/22 Javascript
jQuery插件实现文件上传功能(支持拖拽)
2020/08/27 Javascript
javaScript嗅探执行神器-sniffer.js
2017/02/14 Javascript
ES6正则表达式扩展笔记
2017/07/25 Javascript
Node.js npm命令运行node.js脚本的方法
2018/10/10 Javascript
Vue中UI组件库之Vuex与虚拟服务器初识
2019/05/07 Javascript
Python2.x中str与unicode相关问题的解决方法
2015/03/30 Python
Python多进程分块读取超大文件的方法
2016/04/13 Python
关于Python数据结构中字典的心得
2017/12/04 Python
Python模块WSGI使用详解
2018/02/02 Python
用Eclipse写python程序
2018/02/10 Python
python地震数据可视化详解
2019/06/18 Python
详解用Python为直方图绘制拟合曲线的两种方法
2019/08/21 Python
Python循环实现n的全排列功能
2019/09/16 Python
python创建子类的方法分析
2019/11/28 Python
Python @property原理解析和用法实例
2020/02/11 Python
python3中TQDM库安装及使用详解
2020/11/18 Python
css3 border-image使用说明
2010/06/23 HTML / CSS
CSS3实现歌词进度文字颜色填充变化动态效果的思路详解
2020/06/02 HTML / CSS
Bodum官网:咖啡和茶壶、玻璃器皿、厨房电器等
2018/08/01 全球购物
Nobody Denim官网:购买高级女士牛仔裤
2021/03/15 全球购物
教育学专业实习生的自我鉴定
2013/11/26 职场文书
幼儿教师工作感言
2014/02/14 职场文书
2014年导购员工作总结
2014/11/18 职场文书
党员干部公开承诺书范文
2015/04/27 职场文书
2016年大学生社会实践心得体会
2015/10/09 职场文书