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

变量的作用域说白了就是变量的值从哪里获取,或者说变量取值的地方

我们在写代码过程中会用到很多变量,这些变量会出现在各种代码块中,有的出现在函数块里,有的在函数块外,例如:

在这个例子中,对于变量a,b的值,是应该先识别函数中的还是先识别函数外的呢,其实python内部在识别变量的值得时候是有顺序的,不是胡乱读取的,python内部对于获取变量的值是规定了一个顺序的。

当你在代码里声明一个变量后,python会在LEGB四个作用域里搜索变量的值,它的搜索是有顺序的,第一步现在L也就是当前最里层局部作用域内找,如果没找到,第二步会跑到包含当前层的上一层作用域E里找,还没找到的话,第三部会去模块级的里面G里找,最后是去python里的固定模块里找。

三、在不同作用域修改变量的值

问题1.,既然对于变量的值的搜索有顺序,如果当前在全局里已经有变量a的值,我在局部域里想修改a的值,怎么修改呢?

问题2. 如果变量是在上一层的函数里有值,我想在当前层函数内修改,又如何操作呢?

问题1的解答:对于这种情况需要用到global,在重新给变量a赋值时,要先声明变量global a,

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关键字。

一个程序的所有的变量并不是在哪个位置都可以访问的,访问权限决定于这个变量是在哪里赋值的,变量的作用域决定了在哪一部分程序你可以访问哪个特定的变量名称,两种最基本的变量作用域,第一种是局部变量,第二种是全局变量.定义在函数内部的变量拥有一个局部作用域,定义在函数外的拥有全局作用域,而局部变量只能在其被声明的函数内部访问,全局变量则可以在整个程序范围内访问.

变量的属性与执行依据:

● 在子程序中定义的变量称为局部变量
● 在程序的一开始定义的变量称为全局变量
● 全局变量作用域是整个程序,局部变量作用域是定义该变量的子程序
● 当全局变量与局部变量同名时:在定义局部变量的子程序内,局部变量起作用,在其它地方全局变量起作用
● 当内部作用域想修改外部作用域的变量时,就要用到global和nonlocal关键字了
● 局部变量只能在其被声明的函数内部访问,而全局变量可以在整个程序范围内访问

全局变量: 如下定义并使用一个全局变量,来看一下效果吧.

局部变量: 如下定义并使用一个局部变量,来看一下效果吧.

局部转全局: 将一个局部变量通过global关键字,转换为全局变量.

外层非全局: 如果要修改嵌套作用域(enclosing 作用域,外层非全局作用域)中的变量则需要nonlocal关键字声明一下.

函数是组织好的,可重复使用的,用来实现单一,或相关联功能的代码段,函数能提高应用的模块性,和代码的重复利用率,一个较大的程序一般应分为若干个程序块,每一个模块用来实现一个特定的功能.所有的高级语言中都有子程序这个概念,用子程序实现模块的功能.在Python语言中,子程序的作用是由一个主函数和若干个函数构成,由主函数调用其他函数,其他函数也可以互相调用,同一个函数可以被一个或多个函数调用任意多次.

函数是python为了代码最大程度地重用和最小化代码冗余而提供的基本结构,函数是一种设计工具,它能让程序员将复杂的系统分解为可管理的部件,函数用于将相关功能打包并参数化.

在Python中可以创建如下4种函数:

● 全局函数:定义在模块
● 局部函数:嵌套于其它函数中
● lambda函数:表达式,如需多次调用
● 方法:与特定数据类型关联的函数,并且只能与数据类型关联一起使用

● 函数代码块以def关键词开头,后接函数标识符名称和圆括号()
● 任何传入参数和自变量必须放在圆括号中间,圆括号之间可以用于定义参数
● 函数的第一行语句可以选择性地使用文档字符串,-用于存放函数说明
● 函数内容以冒号起始,并且必须保持缩进,否则会当作普通语句来执行
● return [表达式] 结束函数,选择性地返回一个值给调用方,也就是返回值

定义无参函数: 如下我们编写一个无参数的函数,并在后面直接调用其执行.

定义有参函数: 如下我们编写两个函数,分别给予相应的参数,其返回值则不相同.

默认情况下,参数通过其位置进行传递,从左至右,这意味着,必须精确地传递和函数头部参数一样多的参数,但也可以通过关键字参数、默认参数或参数容器等改变这种机制.

通常Python中所支持的参数传递形式:

● 普通参数:普通参数传递,在定义函数时就指定了规律是从左至右传递
● 默认参数:定义函数时是使用"name=value"的语法直接给变量一个值,从而传入的值可以少于参数个数
● 指定参数:调用函数时指定"name形式参数=value实际参数"的语法通过参数名进行匹配
● 动态参数:在我们定义函数时,形式参数中收集任意多基于普通参数
【定义函数时使用* :收集普通参数,返回元组,*args】【定义函数时使用**:收集指定参数,返回列表,**kwargs】
● 动态参数解包:在调用函数时,使用**开头的参数,从而传递任意多基于普通或指定参数

什么是形式参数和实际参数:

● 形式参数:形参变量只有在被调用时才分配内存单元,在调用结束时,即刻释放所分配的内存单元.因此,形参只在函数内部有效.函数调用结束返回主调用函数后则不能再使用该形参变量
● 实际参数:实参可以是常量、变量、表达式、函数等,无论实参是何种类型的量,在进行函数调用时,它们都必须有确定的值,以便把这些值传送给形参.因此应预先用赋值,输入等办法使参数获得确定值

普通参数传递: 定义一个函数体,并且为其传递三个参数,执行函数并打印结果.

带默认参数传递: 同样的,我们可以给指定的字段添加默认参数,如果用户不输入则默认使用指定参数,这里需要注意的是:如果您要使用带默认参数的函数,则需要把带参数的字段,放在函数最后一项.

动态参数传递(传递列表): 若你的函数在定义时不确定用户想传入多少个参数,就可以使用非固定参数,传递一个列表.

执行函数时有·*,则把所有迭代对象拆分为单个元素作为元组的元素,如传入列表,会把列表中每一个元素遍历添加到元组中当作一个元素,如下可看到差别.

动态参数传递(传递字典): 我们可以使用**kwargs默认参数,来接收一个字典,并通过函数体打印出来.

如上方法是在调用函数的时候传递的字典,当然我们也可以直接将一个现有的字典传递进去.

动态参数传递(万能参数): 我们使用*与**通常情况下可以传递任何值,所以称作万能参数.

拓展补充(1): 其实在python中我们经常看到万能参数,比如str.format()方法,就是一个典型的万能参数的例子,如下演示,了解即可.

拓展补充(2): 在多个同名函数的情况下,默认使用最后后一个函数,最后一个函数名会指向新的内存对象,函数名是函数体在内存中的引用.

拓展补充(3): 函数传递的是指针,所以我们的数据会被保留下来如下例子.

拓展补充(4): 如下例子由于函数没有定义返回值,所以默认为none.

return语句用来实现退出函数,选择性地向调用方返回一个表达式,不带参数值的return语句返回None,之前的例子都没有示范如何返回数值,如下先来看一下返回语句的规则:

● Return 语句用于退出函数,选择性地向调用方返回一个表达式
● 如果Return语句不带任何参数,则不带参数值的Return语句默认返回None
● 函数在执行过程中只要遇到Return,就会停止执行并返回结果,通俗的将遇到ret说明函数结束

默认函数返回: 如下使用默认函数返回,通常情况下返回一个确定数值.

选择性返回: 在函数体内部使用判断结构,如果输入的是偶数返回0,否则返回-1.

返回一个列表: 通过函数体的运算后,将一个列表返回给外部来接收使用.

函数是一段可执行代码,编译后就固化了,每个函数在内存中只有一份实例,得到函数的入口点便可以执行函数了,一个函数可作为另一个函数的参数或返回值,可以赋给一个变量.函数可以嵌套定义,即在一个函数内部可以定义另一个函数,有了嵌套函数这种结构,便会产生闭包问题.

闭包是由函数及其相关的引用环境组合而成的实体(闭包=函数+引用环境)这个从字面上很难理解,Python中的闭包从表现形式上定义(解释)为:如果在一个内部函数里,对在外部作用域(但不是在全局作用域)的变量进行引用,那么内部函数就被认为是闭包(closure).这个定义是相对直白的,好理解的,下面举一个简单的例子来说明.

如上代码,在一个内部函数里:adder(y)就是这个内部函数,对在外部作用域(但不是在全局作用域)的变量进行引用:x就是被引用的变量,x在外部作用域adds里面,但不在全局作用域里,则这个内部函数adder就是一个闭包.闭包=函数块+定义函数时的环境,adder就是函数块,x就是环境,当然这个环境可以有很多,不止一个简单的x.

闭包返回函数: 通过使用闭包,返回一个函数,并使用函数做进一步的计算.

闭包选择返回: 通过返回值判断,来使用不同的闭包函数,从而返回不同的结果.

闭包返回函数列表: 通过使用闭包函数,一次性返回多个函数列表,每个函数拥有独立空间.

除了函数的闭包以外,函数还支持两种调用方式,一种是嵌套函数,另一种是递归函数,这里需要注意的是,最好在开发中尽量少用这样的结构,这种结构一旦层数变多将很难后期进行维护,所以你懂的.

嵌套函数:即指在一个函数体中,嵌套另外一个函数体,内部函数执行后将结果返回给外部函数使用
递归函数:函数在其内部调用它自己,就叫做递归,但递归需设置退出条件,不然会一直递归下去,变成一个死循环

嵌套函数: 定义一个嵌套函数,并打印出其执行轨迹,并理解其原理.

递归函数(1): 使用递归的方式实现指定数字的阶乘,如下所示.

if 0==n: # n=0 的话直接返回空,对用户输入的零进行判断 f(5)的执行过程如下

递归函数(2): 如下实例传入一个数据,每次递归增加1,直到满足x>=5则结束递归.

递归函数(3): 如下实例,传入一个数据,每次在数据本身除以2直到除数为1则停止.

小总结: 使用递归实现二分法,也就是折半查找法.

python 使用lambda来创建匿名函数,所谓匿名即不再使用 def 语句这样标准的形式定义一个函数.

匿名函数的几个注意事项:

● lambda只是一个表达式,函数体比 def 简单很多
● lambda函数拥有自己的命名空间,且不能访问自己参数列表之外或全局命名空间里的参数
● lambda的主体是一个表达式,而不是一个代码块,仅仅能在lambda表达式中封装有限的逻辑进去
● lambda函数看起来只能写一行,却不等同于C或C++的内联函数,应该区别对待,毕竟是两们不同语言.

定义匿名函数(1): 使用功能最基本的语法定义一个匿名函数.

定义匿名函数: 对于简单的函数,也存在一种简便的表示方式,即:lambda表达式.

# 定义函数(普通方式) # 定义函数(lambda表达式)

向匿名函数传递列表: 想一个匿名函数传递一个列表,并分别计算后返回相应数据.

◆函数编写实例(拓展)◆

拓展(1): 编写函数,计算传入字符串中数字、字母、空格、其他的个数,并统计出来. 

拓展(2): 编写函数,判断用户传入的对象(字符串、列表、元组)的长度是否大于5,并输入相关状态.

拓展(3): 编写函数,检查用户传入的对象(字符串、列表、元组)的每一个元素是否含有空内容.

拓展(4): 编写函数,检查获取传入列表或元组对象的所有奇数位索引对应的元素,并将其作为新列表返回给调用者.

拓展(5): 编写函数,检查传入字典的每一个value的长度,如果大于2,那么仅保留前两个长度的内容,并将新内容返回给调用者.

help(): 详细查看某个类有那些方法或者方法的具体使用.

dir(): 查看指定类型,支持的方法或具体使用.

int(): 实例化数字类型,或将其他类型转换为数字类型,或各种进制转换为十进制.

float(): 实例化浮点类型,或将数字字符串转换为浮点型,仅限于数字字符串.

(1) 实例化浮点类型
(2) 将数字字符串转换为浮点类型

str(): 实例化字符串类型,或将其他类型转换为字符串类型.

(1) 实例化字符串类型
(2) 将其他类型转换为字符串类型了
#注意:列表格式或字典格式的字符串类型转换为列表或者字典需要使用json模块

list(): 将其他类型转为列表类型.

(1) 实例化列表类型
(2) 将其他类型转换为列表

tuple(): 实例化元组类型,或将其他类型转换为元组类型.

(1) 实例化元组类型
(2) 将其他类型转换为元组类型

dict(): 实例化字典,或将元组列表转换为字典类型仅限元组形式列表类型.

(1) 实例化字典类型
(2) 将元组形式的列表转换为字典
#注意:zip()这个内置方法可以将两个列表生成元组形式列表类型

set(): 实例化可变集合类型,或其他类型转换成集合类型.

(1) 实例化集合类型
(2) 将其他类型转换成集合set类型

frozenset(): 实例化不可变集合,或类型转换成不可变集合类型.

(1) 实例化不可变集合
(2) 类型转换成不可变集合

bytes(): 将字符串类型转换成字节byte类型,在计算机底层都是以二进制存储数据的.

(1) 将字符串转换为字节类型
(2) 将字节类型重新转换为字符串

open(): 打开一个文件对象,用于对文件的操作处理.

type(): 查看某个实例属于哪个类型.

len(): 查看实例中的长度,说白点就是元素个数,以为单位字符计算.

input(): 默认输入的格式为字符串,输入的数字也是字符串,需要int()转换.

all(): 接收一个迭代对象,当对象中的元素全部为真的时候,才会返回真.

any(): 只要有一个为真则为真,全为假则为假.

divmod(): 除法得余数,在分页功能中会用到.

enumerate(): 枚举类型,实现循环的时候打印出行号,默认是0开始,也可以设置1开始.

slice(): 和字符串列表的切片的功能是一样的.

zip(): 取一个或多个序列为参数,把序列中的并排元素配成元组,返回元组形式的列表类型,当元素长度不同时以最短序列的长度为准.

# 两个列表合成一个字典 #注意:这个地方在“+”号两边不能有空格,也就是不能写成"1 + 2j",应该是"1+2j",否则会报错

format(): 格式化输出的,和百分号是同样的功能.

#注意: 对于简单的函数用lambda可以实现

map(): map(函数,可迭代的对象),循环第二个参数,将每一个元素执行第一个函数,就把返回值存入结果result中.

#注意: 对于简单的函数用lambda可以实现

next(): 用于遍历迭代器,for循环就是调用next()实现,不过当使用next()遍历时,迭代器没有元素时会报错,for则不会.

我要回帖

更多关于 Python全局变量的作用域是 的文章

 

随机推荐