Python 变量作用域 LEGB
回顾 - Decorator
前篇有讲到了, 闭包和装饰器的概念. 闭包就是, 函数内部嵌套函数. 而 装饰器只是闭包的特殊场景而已, 特殊在如果外函数的参数是指向一个, 用来被装饰的函数地址时(不一定是地址哈, 随意就好) , 就有了 "@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 变量作用域 LEGB的更多相关文章
- Python 变量作用域 LEGB (下)—— Enclosing function locals
上篇:Python 变量作用域 LEGB (上)—— Local,Global,Builtin https://www.cnblogs.com/yvivid/p/python_LEGB_1.html ...
- Python 变量作用域 LEGB (上)—— Local,Global,Builtin
Python 变量作用域的规则是 LEGB LEGB含义解释:L —— Local(function):函数内的名字空间E —— Enclosing function locals:外部嵌套函数的名字 ...
- python变量作用域
[python变量作用域] 几个概念: python能够改变变量作用域的代码段是def.class.lamda. if/elif/else.try/except/finally.for/while 并 ...
- Python 变量作用域与函数
Python 的创始人为吉多·范罗苏姆(Guido van Rossum).1989年的圣诞节期间,吉多·范罗苏姆为了在阿姆斯特丹打发时间,决心开发一个新的脚本解释程序,作为ABC语言的一种继承.Py ...
- Python基本语法_变量作用域LEGB
目录 目录 软件系统 变量的作用域 高级语言对数据类型的使用过程 作用域的产生 作用域的类型 Llocal局部作用域 Eenclosing嵌套作用域 Gglobal全局作用域 Bbuilt-in内置作 ...
- python函数作用域LEGB
我们的在学习Python函数的时候,经常会遇到很多定义域的问题,全部变量,内部变量,内部嵌入的函数,等等,Python是如何查找的呢?以及Python又是按照什么顺序来查找的呢?这里做一个顺序的说明 ...
- python——变量作用域及嵌套作用域
----------------------------------------------------------------------------- 前言-------------------- ...
- Python 变量作用域和列表
变量作用域 变量由作用范围限制 分类:按照作用域分类 全局(global):在函数外部定义 局部(local):在函数内部定义 变量的作用范围: 全局变量:在整个全局范围有效 全局碧昂量在局部可以使用 ...
- python 变量作用域、闭包
先看一个问题: 下面代码输出的结果是0,换句话说,这个fucn2虽然已经用global声明了variable1,但还是没有改变变量的值 def func1(): variable1=0 def fun ...
- python变量作用域,函数与传参
一.元组传值: 一般情况下函数传递参数是1对1,这里x,y是2个参数,按道理要传2个参数,如果直接传递元祖,其实是传递一个参数 >>> def show( x, y ): ... p ...
随机推荐
- ATT&CK实战系列(一)
环境下载 下载靶场环境,并导入虚拟机分别是win2003.win7.winserver2008 配置网络 虚拟机--编辑--虚拟机网络编辑器--添加网络VMnet2--仅主机模式分配的地址是192.1 ...
- JS数组相减
const arr1 = [1, 2, 3] const arr2 = [1, 3] const arr3 = arr1.filter(v => !arr2.includes(v)) // [2 ...
- go json omitempty 关键字 脱坑
用法 大家对于 json 和 struct 之间的转换一定不陌生,为了将代码中的结构体与 json 数据解耦,通常我们会在结构体的 field 类型后加上解释说明,例如在表示一个地址的时候, json ...
- Django实战项目-学习任务系统-定时任务管理
接着上期代码框架,开发第4个功能,定时任务管理,再增加一个学习定时任务表,主要用来设置周期重复性的学习任务,定时周期,定时时间,任务标题和内容,预计完成天数,奖励积分和任务状态等信息. 现实中学习一门 ...
- Docker镜像介绍
一.Docker镜像介绍 镜像是Docker的三大核心概念之一. Docker运行容器前需要本地存在对应的镜像,如果镜像不存在本地,Docker会尝试先从默认的镜像仓库下载(默认使用Docker Hu ...
- CentOS7安装图形界面模式
0.9272019.03.24 15:17:05字数 865阅读 22,115 本人通过VMware14安装Centos7.6过程中出现蓝屏现象,最后发现是因为在系统安装图形界面过程中报错导致,所以决 ...
- SpreadJS V18.0 新版本发布!数据驱动革新,效率与体验全面升级
表格控件SpreadJS推出V18.0及V8.0版本!本次更新聚焦数据管理.多语言适配.报表与透视表增强,新增多项重磅功能,赋能企业高效应对复杂业务场景.核心亮点速览 一.表格绑定数据源:直连数据管理 ...
- Tinyhttpd 源代码初步解读
Tinyhttpd 是很早以前的一个 web 服务器程序,由 C 语言编写,整个程序十分小巧,源码只有几百行.它一般不适合用于生产环境,因为它很简单,只实现了读取 html 以及 Get / POST ...
- .NET & JWT
使用 JWT 库 JWT,a JWT(JSON Web Token) implementation for .NET 该库支持生成和解析JSON Web Token 你可以直接通过Nuget获取,也可 ...
- 航天信息诺税通SAAS接口封装DLL
项目中需要对接航天信息的诺税通接口开具电子发票,为此将功能封装到了DLL中,其他项目也可以方便的引用. Delphi调用示例: 有需要可以和我联系:yzqnet(微信)