python里面变量作用域是什么?

Python中if语句内元组变量的作用域


在其他编程语言中,您可以在语句外初始化变量,但是这种情况呢?

重要提示:我需要处理一个元组,而不是一个列表。

更新:我不需要这种情况x == y,因此我更改了代码,添加了进一步的条件:



Python没有阻止范围。元组和列表功能在这里无关紧要,所用类型的选择也一样。

foo在此处未定义的唯一原因是因为您忘记了一个条件:x和y相等时。

站长简介:高级工程师,爱好交友,无偿辅导python和前端,技术交流,面试指导,找工作指导,瞎聊都可加我微信i88811i哈,欢迎欢迎!也欢迎加入程序员交流群,专属程序员的圈子,加我微信拉你进群
欢迎关注我的公众号:程序员总部,关注公众号回复python,免费领取,关注公众号回复充值+你的账号,免费为您充值1000积分


所属网站分类: 技术文章 >


python 作用域分成四种 L(Local):最内层,包含局部变量,比如一个方法内部。 E(Enclosing):包含了非局部(non-local)也非全局(non-global)的变量。比如两个嵌套函数,一个函数(或类) A 里面又包含了一个函数 B ,那么对于 B 中的名称来说 A 中的作用域就为 nonlocal。 G(Global):当前脚本的最外层,比如当前模块的全局变量。 B(Built-in):包含了内建的变量/关键字等。,最后被搜索

这个案例里面,第一个num是全局变量,第二个是局部变量,第三个是嵌套变量。内建变量这里没有,内建变量都是python系统内部定义好的变量。

变量的访问顺序遵循LEGB原则 L(局部)> E(内嵌)> G(全局)> B(内建)。这里的变量类型是一个相对的概念,第三个num相对于inner这个函数是局部变量,第二个num相对于inner是局部变量。所以是否是局部变量是相对于当前的作用域而言的。而能创建作用域的只有模块,类和函数。其它的代码块(如 if/elif/else/、try/except、for/while等)是不会引入新的作用域的,也就是说这些语句内定义的变量,外部也可以访问,比如下面的例子:

虽然变量有四种类型,但是访问一个变量可以简单的分为两种类型,局部变量和非局部变量。如果是局部变量,则在当前作用域访问的就是该局部变量,如果是非局部变量访问的就是离改作用域最近的局部变量,这个最近遵循LEGB原则。

这个案例里面输出的arg1是非局部变量,根据LEGB原则arg1应该是90。arg2是局部变量,输出99。

这个案例会输出什么?什么都不会输出,直接报错,语法都过不了。如果改成下面这个样子就不会报错。

原因就在num = num + 1这里,如果是num = 1 ,那num是局部变量。同样num = num + 1这里num也是局部变量,但是局部变量还没任何值,然后又做加1操作,语法上就是错误的。那如何让num = num + 1有效呢?可以使用nonlocal。nonlocal的使用后面会介绍。

上面介绍的都是脚本里面的局部变量,类里面的变量和脚本不一样。看下面这个案例:

上面这个案例最后输出结果是11,因为返回的num是全局变量。如果getTotal方法改成下面这种形式:

输出结果是1,这个时候访问的是示例属性,再改成下面这种形式:

输出结果是15,这个时候访问的是类属性。

类里面访问的变量判断方法其实和脚本是一样的,但是首先要把实例属性和类属性区分出来,有self的是实例属性,带‘类名.’的是类属性(可以看我前面的文章),如果既不是实例属性也不是类属性,那访问判断方式和脚本是一样的。

global和nonlocal关键字 如果想要指定访问的变量是全局变量可以使用global关键字

如果是在函数嵌套里面想要使用外层函数的变量,则要用nonlocal关键字。

这里如果把nonlocal改为global输出的结果就是31。所以global和nonlocal的区别就是,global是争对全局变量,nonlocal是争对嵌套函数里面的上级变量。

总结一下:访问一个变量前需要先知道这个变量是什么变了,局部还是非局部,如果是非局部,则根据LEGB原则访问的就是最近的非局部变量。如果想指定访问的是哪个变量,全局可以使用global关键字,嵌套函数上级变量可以使用nonlocal关键字。

先来介绍一下LEGB规则和命名空间。

命名空间,英文名字:namespaces。

由于Python一切皆对象,而对象很多时候直接使用并不是很方便,所以要给它取一个名字。

打个比方,我们常常使用的手机,我们买手机的时候经常会给销售说: ''我看一下华为 夏日胡杨色 Mate40Pro 手机",这就是手机的对象。但是我们平时在提及的时候,总是说:“华为 夏日胡杨色 Mate40Pro 手机",是不是太麻烦了?于是我们人类就为这个世界上的各种对象命名,比如将"华为 夏日胡杨色 Mate40Pro 手机”称之为"手机"。

在编程中也是如此,为了更好的表示变量与引用对象的关系,我们常常通过变量赋值完成它们之间的映射。

上面这个代码,5就是一个计算机内存中存在的对象,使用id()内置函数可以查看对象在内存中的地址。a 就是为这个对象赋予的别名,如前面所讲,它是与内存中一个编号为的对象关联,这个对象就是5。

所以命名空间是从所定义的命名到对象的映射。因此大部分的命名空间都是通过 Python 字典来实现的,字典内保存了变量名称与对象之间的映射关系不同的命名空间,可以同时存在,但彼此相互独立互不干扰。

  • 全局名称(global names),模块中定义的名称,记录了模块的变量,包括函数、类、其它导入的模块、模块级的变量和常量。
  • 局部名称(local names),函数中定义的名称,记录了函数的变量,包括函数的参数和局部定义的变量。(类中定义的也是)

如果我们要搜索一个变量a,查找顺序为:局部的命名空间 -> 全局命名空间 -> 内置命名空间。从内圈向外圈查找。

如何来查看局部和全局的变量有哪些呢?我们通常使用 Python 内置的函数:globals() 和 locals()

但是假设还有其他的函数比如:func1(),func2(),....都需要添加日志记录,这时我们可能会在每个函数中添加类似的一句话:

注意:logging模块提供的日志记录函数所使用的日志器设置的日志级别是WARNING,因此只有WARNING级别的日志记录以及大于它的ERROR和CRITICAL级别的日志记录被输出了,而小于它的DEBUG和INFO级别的日志记录被丢弃了。

但是这样就造成大量雷同的代码,为了减少重复写代码,我们可以重新定义一个函数:专门处理日志 ,日志处理完之后再执行真正的业务代码。

有人给出这样的解决方案:

逻辑上应该不难理解, 但是这样的话,我们每次都要将一个函数作为参数传递给record_log函数,最主要的是之前执行业务逻辑时,执行运行add()或者func1()的地方,但是现在不得不改成record_log(参数)的调用了。但是这样会破坏原来的代码,如果你的代码量很多很多的话,修改起来则有是灾难。当然我们还有更好的解决发方式就是:装饰器。

来看一个简单的装饰器,实现模拟:日志打印器

日志就是你调用了哪些方法的一个记录,就类似我们每天做的事情要通过日记记录下来一样

# 有一个求和的函数,给其添加装饰器

哈哈哈,我调用了add很棒吧!点赞!

其中@符号是装饰器的语法糖,在定义函数的时候使用,避免再一次赋值操作。

我们来看一下使用装饰器的原理是什么?

''' 我是大名鼎鼎的托尼 '''

我是斯塔克工业的CEO

然后这段代码,写起来有点麻烦,Python官方出了一个快捷代码,也就是语法糖@,用了语法糖就变成了下面这样

''' 我是大名鼎鼎的托尼 '''

这样对于函数调用本身是没有变化的,因为调用 Tony()的函数名没有变化。看下面的代码输出结果是什么?

''' 我是大名鼎鼎的托尼 '''

我是一副帅气的铠甲...

我是斯塔克工业的CEO

为什么是这个结果呢?那就是使用语法糖装饰 Tony 之后,代码执行顺序就变成了:

  1. 首先打印:我是一副帅气的铠甲...
  2. 接下来打印:装甲穿好了!!
  3. 打印:我要变身喽! 我变成钢铁侠了 我是斯塔克工业的CEO这三句话.

当然还有有参数的装饰器, 类装饰器等.我们就不给大家展开介绍了。

# 上面代码的基础上我们打印查看一下
 

如果不加装饰器看到的结果是:

大家发现加上装饰器之后原函数的元信息不见了,比如名字改变,文档改变等。如何解决这个问题呢?

使用functools.wraps,wraps本身也是一个装饰器,它能把原函数的元信息拷贝到装饰器函数中,这使得装饰器函数也有和原函数一样的元信息了。

''' 我是大名鼎鼎的托尼 '''

再次打印结果就会是原有的 Tony函数 的结果了。

Tips:但是不是必须加的,如果元信息没用,可以不添加的,根据自己的开发需求了。

如果想要获取Python学习资料、源码的同学们,请用手机点击下方链接,获取海量资料包哟!

我要回帖

更多关于 python函数变量的作用域 的文章

 

随机推荐