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 json encode datetime类型
Dec 28 Python
Python学习笔记之常用函数及说明
May 23 Python
理解生产者消费者模型及在Python编程中的运用实例
Jun 26 Python
pyenv命令管理多个Python版本
Mar 26 Python
python实现12306火车票查询器
Apr 20 Python
Python使用修饰器执行函数的参数检查功能示例
Sep 26 Python
python中多个装饰器的执行顺序详解
Oct 08 Python
Python XML转Json之XML2Dict的使用方法
Jan 15 Python
python 字典操作提取key,value的方法
Jun 26 Python
python 利用toapi库自动生成api
Oct 19 Python
Python异常类型以及处理方法汇总
Jun 05 Python
Python图像处理库PIL详细使用说明
Apr 06 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
初探PHP5
2006/10/09 PHP
站长助手-网站web在线管理程序 v1.0 下载
2007/05/12 PHP
PHP如何编写易读的代码
2007/07/10 PHP
php简单socket服务器客户端代码实例
2015/05/18 PHP
高质量PHP代码的50个实用技巧必备(上)
2016/01/22 PHP
thinkphp3.2中实现phpexcel导出带生成图片示例
2017/02/14 PHP
jQuery中对节点进行操作的相关介绍
2013/04/16 Javascript
javascript截取字符串(通过substring实现并支持中英文混合)
2013/06/24 Javascript
jquery+css3实现网页背景花瓣随机飘落特效
2015/08/17 Javascript
jQuery模拟物体自由落体运动(附演示与demo源码下载)
2016/01/21 Javascript
基于jQuery实现咖啡订单管理简单应用
2017/02/10 Javascript
Angular.JS去掉访问路径URL中的#号详解
2017/03/30 Javascript
JavaScript瀑布流布局实现代码
2017/05/06 Javascript
javascript  数组排序与对象排序的实例
2017/07/17 Javascript
深入理解基于vue-cli的vuex配置
2017/07/24 Javascript
Vue2.0 组件传值通讯的示例代码
2017/08/01 Javascript
Vue微信项目按需授权登录策略实践思路详解
2018/05/07 Javascript
详解基于Vue-cli搭建的项目如何和后台交互
2018/06/29 Javascript
JS实现全屏预览F11功能的示例代码
2018/07/23 Javascript
vue router导航守卫(router.beforeEach())的使用详解
2019/04/19 Javascript
Node.js系列之连接DB的方法(3)
2019/08/30 Javascript
解决vue cli使用typescript后打包巨慢的问题
2019/09/30 Javascript
javascript如何使用函数random来实现课堂随机点名方法详解
2020/07/28 Javascript
js实现复制粘贴的两种方法
2020/12/04 Javascript
Django1.3添加app提示模块不存在的解决方法
2014/08/26 Python
使用Python进行目录的对比方法
2018/11/01 Python
Python3+Pycharm+PyQt5环境搭建步骤图文详解
2019/05/29 Python
python返回数组的索引实例
2019/11/28 Python
基于python调用jenkins-cli实现快速发布
2020/08/14 Python
Python控制鼠标键盘代码实例
2020/12/08 Python
世界最大域名注册商:GoDaddy
2016/07/24 全球购物
护士自我评价
2014/02/01 职场文书
《台湾的蝴蝶谷》教学反思
2014/02/20 职场文书
《彼得与狼》教学反思
2016/02/20 职场文书
教师实习自我鉴定总结
2019/08/20 职场文书
一行Python命令实现批量加水印
2022/04/07 Python