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


猜你喜欢
- PHP hebrev() 函数实例反向显示希伯来字符:<?php echo hebrev("á çù&
- 原文地址第一次翻译文章,请各路人士多多指教!类型和接口因为映射建设在类型的基础之上,首先我们对类型进行全新的介绍。go是一个静态性语言,每个
- 装饰器(decorator)是一种高级Python语法。装饰器可以对一个函数、方法或者类进行加工。在Python中,我们有多种方法对函数和类
- 前言在c++中参数传递有两种形式:值传递和引用传递。这两种方式的区别我不在此说,自行补上,如果你不知道的话。我先上python代码,看完我们
- 1. 将图片存入数据库关于数据库基本操作的学习,请参见这一篇文章:https://www.jb51.net/article/167141.h
- 具体内容如下:1 os.system例如 ipython中运行如下命令,返回运行状态statusos.system('cat /et
- 一、业务背景在金融风控领域,常常使用KS指标来衡量评估模型的区分度(discrimination),这也是风控模型最为追求的指标之一。下面将
- 前言前几天切换了node版本,顺带着升级了npm,今天使用nodemon启动node环境时发现报错,提示未安装。没安装就安装了下,结果!冒出
- 新一代GPT4发布了,今年GPT不断给大家带来惊喜。在OpenAI的官网,也公开了GPT的Python调用第三方库:openai。今天我们就
- 小程序大功能对一批报文要处理要处理里面的得分,发现python linecache ,特记录如下。#!/usr/bin/env python
- 今天遇到了这样一个问题,使用matplotlib绘制热图数组中横纵坐标自然是图片的像素排列顺序,但是这样带来的问题就是画出来的x,y轴中坐标
- UDP 客户端一个使用UDP协议的客户端示例代码,用于实现连续对话。请注意,UDP是无连接协议,因此在实现连续对话时需要特别小心。以下是示例
- 本文通过一个详细的例子,来阐述了在线编辑XML文档数据的方法。由于Netscape对XML的支持比较弱,因此,要实现跨平台的数据交换,数据的
- 前言在vue里,组件之间的作用域是独立的,父组件跟子组件之间的通讯可以通过prop属性来传参,但是在兄弟组件之间通讯就比较麻烦了。比如A组件
- flask之模板继承为什么要用模板继承?原因很简单,因为模板继承能让我们在实现效果的前提下少些很多代码!咱废话不多说,先来看个小例子,看完我
- 问题python中 import ** 成功,但是不能调用它的函数或者属性,如下图所示 原因log 和 setting 在pyth
- groupby官方解释DataFrame.groupby(by=None, axis=0, level=None, as_index=Tru
- 本文实例讲述了Python数据分析之双色球统计单个红和蓝球哪个比例高的方法。分享给大家供大家参考,具体如下:统计单个红球和蓝球,哪个组合最多
- 如下所示:#!/usr/bin/env python# -*- coding: utf-8 
- SQL Server数据库日志清除的两个方法:方法一一般情况下,SQL数据库的收缩并不能很大程度上减小数据库大小,其主要作用是收缩日志大小,