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中类的继承代码实例
Oct 28 Python
详解Python中如何写控制台进度条的整理
Mar 07 Python
python实现聊天小程序
Mar 13 Python
PyQt5实现无边框窗口的标题拖动和窗口缩放
Apr 19 Python
python批量复制图片到另一个文件夹
Sep 17 Python
详解配置Django的Celery异步之路踩坑
Nov 25 Python
对python 合并 累加两个dict的实例详解
Jan 21 Python
python读写csv文件实例代码
Jul 05 Python
python如果快速判断数字奇数偶数
Nov 13 Python
python 画3维轨迹图并进行比较的实例
Dec 06 Python
使用PyTorch实现MNIST手写体识别代码
Jan 18 Python
Python的property属性详细讲解
Apr 11 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析构函数的简单使用说明
2015/08/24 PHP
PHP实现基于文本的摩斯电码生成器
2016/01/11 PHP
PHP实现简单登录界面
2019/10/23 PHP
javascript的对话框详解与参数
2007/03/08 Javascript
JavaScript语法着色引擎(demo及打包文件下载)
2007/06/13 Javascript
javascript demo 基本技巧
2009/12/18 Javascript
jquery DOM操作 基于命令改变页面
2010/05/06 Javascript
jquery实现漫天雪花飞舞的圣诞祝福雪花效果代码分享
2015/08/20 Javascript
基于PHP和Mysql相结合使用jqGrid读取数据并显示
2015/12/02 Javascript
JS排序方法(sort,bubble,select,insert)代码汇总
2016/01/30 Javascript
jQuery四种选择器使用及示例
2016/06/05 Javascript
AngularJS 遇到的小坑与技巧小结
2016/06/07 Javascript
探究Vue.js 2.0新增的虚拟DOM
2016/10/20 Javascript
jQuery第一次运行页面默认触发点击事件的实例
2018/01/10 jQuery
vue2.0 自定义组件的方法(vue组件的封装)
2018/06/05 Javascript
Vue表单及表单绑定方法
2018/09/04 Javascript
vue element自定义表单验证请求后端接口验证
2019/12/11 Javascript
Vue解析剪切板图片并实现发送功能
2020/02/04 Javascript
在vant 中使用cell组件 定义图标该图片和位置操作
2020/11/02 Javascript
[14:03]2017DOTA2亚洲邀请赛开幕式:12神兵演绎水墨中华
2017/04/01 DOTA
python中scikit-learn机器代码实例
2018/08/05 Python
pandas DataFrame运算的实现
2020/06/14 Python
L’AGENCE官网:加州女装品牌
2018/06/03 全球购物
Charlotte Tilbury澳大利亚官网:英国美妆品牌
2018/10/05 全球购物
英国领先的露营和露营车品牌之一:OLPRO
2019/08/06 全球购物
中科前程Java笔试题
2016/11/20 面试题
幼儿园门卫岗位职责
2014/02/14 职场文书
网络技术专业求职信
2014/05/02 职场文书
毕业生实习期转正自我鉴定
2014/09/26 职场文书
2014城乡环境综合治理工作总结
2014/12/19 职场文书
电力培训学习心得体会
2016/01/11 职场文书
《植物妈妈有办法》教学反思
2016/02/23 职场文书
学术会议开幕词
2016/03/03 职场文书
python pygame入门教程
2021/06/01 Python
JavaScript中的宏任务和微任务详情
2021/11/27 Javascript
React四级菜单的实现
2022/04/08 Javascript