Python学习笔记14

chapter_14

作用域


作用域法则

  • 内嵌的模块是全局作用域: 每个模块都有自身的作用域,这个作用域相对于自身内部是全局作用域.
  • 全局作用域的作用范围仅限于单个文件: 这里的全局指的是在一个文件的顶层的变量名仅对于这个文件内部的代码而言是全局的,变量名由模块文件隔开,必须精确地导入一个模块文件才能够使用这个文件中定义的变量名.
  • 每次对函数的调用都创建了一个新的本地作用域: 每次调用函数,都创建了一个新的本地作用域,也就是说,将会存在由那个函数创建的变量的命名空间.
  • 赋值的变量名除非声明为全局变量或非本地变量,否则皆为本地变量: 默认情况下,所有函数内部定义的变量名位于本地作用域内的,如果需要给一个在函数内部定义的却在模块文件顶层的变量赋值,需要在函数内部用global语句声明.
  • 所有的变量名都可以归纳为本地,全局或者内置的:在函数内部定义的尚未赋值的变量是一个在一定范围内(def内部)的本地变量;在一个模块的命名空间内部定义的是全局变量;由Python预定义的__builtin__模块提供的内置变量.
注意:原处修改并不会把全局变量划分为本地变量,只有赋值后才会改变.

如:变量名L在模块的顶层被赋值为一个列表,在函数的内部的像L.append(X)这样的语句并不会将L划分为本地变量,而L=X却可以.


变量名解析:LEGB原则

三条简单原则:

  • 变量名引用分为三个作用域进行查找:首先是本地,之后是函数内(如果有的话),之后全局,最后是内置.
  • 在默认情况下,变量名赋值会创建或者改变本地变量.
  • 全局声明和非本地声明将赋值的变量名映射到模块文件内部的作用域.

解析:

  • 当在函数中使用未认证的变量名时,Python搜索4个作用域[本地作用域(L),之后是上一层结构中def或lambda的本地作用域(E),之后是全局作用域(G),最后是内置作用域(B)],并且在第一处能够找到这个变量名的地方停下来.如果变量名在这次搜索中没有找到,会报错.
  • 当在函数中给一个变量名赋值时(而不是在一个表达式中对其进行引用),Python总是创建或改变本地作用域的变量名,除非它已经在那个函数中声明为全局变量.
  • 当在函数之外给一个变量名赋值时(也就是说,在一个模块文件顶层,或者是在交互提示模式下),本地作用域和全局作用域是相同的.

内置作用域

  • 内置作用域仅仅是一个名为__builtin__的内置模块,但是必须要import_builtin_之后才能使用内置作用域,因为变量名builtin本身并没有预先内置.

作用域实例

#Global scope
X = 99

def func(Y):
    #Local scope
    Z = X + Y #X is a global
    return Z

func(1)  #func in module:result=100
  • 全局变量名:X,func
  • 本地变量名:Y,Z

X = 88  #全局变量

def func():
    X = 99  #新创建的局部变量,屏蔽全局变量,与全局变量是两个不同的变量

func()
print(X) #打印结果为88

global语句

  • global不是一个类型或大小的声明,它是一个作用域的声明

gloabal使用总结:

  • 全局变量是位于模块文件内部的顶层的变量名.
  • 全局变量如果是在函数内被赋值的话,必须经过声明.
  • 全局变量名在函数的内部不经过声明也可以被引用.
X = 88  #全局变量

def func():
    global X #声明X为全局变量,这样就不会自行创建本地变量
    X = 99

func()
print(X) #结果为:99
y,z = 1,2 
def all_global():
    global x #声明全局变量x
    x = y + z #在经过LEGB法则没有找到x变量后,赋值语句会自动创建x变量.

最小化文件间修改

#first.py
X = 99

#second.py
import first
print(first.X)
first.X = 88 #修改第一个文件的变量
  • 尽管这样的跨文件变量在Python中总是可能修改的,但这样会让两个文件有过于强的相关性,因为它们都与变量X的值相关.这种隐含的跨文件依赖性,在最好的情况下会导致代码不灵活,最坏的情况下会引发bug.

  • 在文件间进行通信最好的办法就是通过调用函数,传递参数然后得到返回值

#first.py
X = 99

def setX(new):
    global X
    X = new;

#second.py
import first
first.setX(88) #修改第一个文件的变量
  • 虽然我们无法避免修改文件间的变量,但是通常的做法是最小化文件间变量的修改.

嵌套作用域

对于一个函数:

  • 一个引用(X)首先在本地(函数内)作用域查找变量名X;之后会在代码的语法上嵌套了的函数中的本地作用域,从内到外查找;之后查找当前的全局作用域(模块文件);最后在内置作用域内(模块__builtin__)查找.
  • 在默认情况下,一个赋值(X = value)创建或改变了变量名X的当前作用域.如果X在函数内部声明为全局变量,它将会创建或改变变量名X为整个模块的作用域.另一方面,如果X在函数内声明为nonlocal,赋值会修改最近的嵌套函数的本地作用域中的名称X.
X = 99

def f1():
    X = 88
    def f2():
        print(X)
    f2()
f1() #打印88
  • f2是一个临时函数,仅在f1内部执行过程中存在(并且只对f1中的代码可见)
  • 当打印变量X时,X引用了存在于函数f1整个本地作用域内的变量X的值.因为函数能够在整个def声明内获取变量名,通过LEGB查找法则,f2内的X自动映射到了f1的X.

  • 嵌套作用域的查找在嵌套的函数已经返回后也是有效的.
def f1():
    X = 88
    def f2():
        print(X)
    return f2

action = f1() #返回的是函数对象
action() #调用函数,打印88
  • 命名为f2的函数的调用动作的运行是在f1运行后发生的,f2记住了在f1中嵌套作用域中的X,尽管f1已经不处于激活状态.

工厂函数

  • 一个能够记住嵌套作用域的变量值的函数,尽管那个作用域或许已经不存在了,这种行为有时也叫做闭合或者工厂函数
#定义了一个外部的函数,这个函数简单地生成并返回了一个嵌套的函数
>>> def maker(N):   
...   def action(X):
...      return X ** N
...   return action
... 
>>> f = maker(2) #构建一个计算平方的工厂函数
>>> f
<function maker.<locals>.action at 0xb74da92c>
>>> f(2) 
4
>>> f(3)
9
>>> g = maker(3) #构建一个计算立方的工厂函数
>>> g
<function maker.<locals>.action at 0xb74da8e4>
>>> g(2)
8
>>> g(4)
64
>>> def func():
...   x = 4
...   action = (lambda n: x ** n) #用匿名函数来构建工厂函数
...   return action
... 
>>> x = func()
>>> print(x(2))
16

nonlocal语句

  • nonlocal和global一样,声明了将要在一个嵌套的作用域中修改的名称.和global的不同之处在于,nonlocal应用于一个嵌套的函数的作用域中的一个名称,而不是所有def之外的全局模块作用域;而且在声明nonlocal名称的时候,它必须已经存在于该嵌套的作用域中–它们可能只存在于一个嵌套的函数中,并且不能由一个嵌套的def中的第一次赋值创建.

  • nonlocal语句只在一个函数内有意义:

def func():
    nonlocal name1,name2,...

nonlocal应用

>>> def tester(start):
...    state = start
...    def nested(label):
...       print(label,state) #可以引用上一层本地作用域的变量
...    return nested
... 
>>> F = tester(0)
>>> F('spam')
spam 0
>>> F('ham')
ham 0
>>> def tester(start):
...   state = start
...   def nested(label):
...      print(label,state)
...      state += 1 #不能修改上一层本地作用域的变量
...   return nested
... 
>>> F = tester(0)
>>> F('spam')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 4, in nested
UnboundLocalError: local variable 'state' referenced before assignment
  • 使用nonlocal进行修改
>>> def tester(start):
...   state = start
...   def nested(label):
...      nonlocal state #使用nonlocal修饰上一层本地作用域的变量
...      print(label,state)
...      state += 1  #这里可以修改
...   return nested
... 
>>> F = tester(0)
>>> F('spam')
spam 0
>>> F('ham')
ham 1
  • nonlocal 与 global 的区别
>>> def tester(start):
...   def nested(label):
...      nonlocal state  #这里出错了,nonlocal 必须声明一个已经存在的变量
...      state = 0
...      print(label,state)
...   return nested
... 
  File "<stdin>", line 3
SyntaxError: no binding for nonlocal 'state' found
>>> def tester(start):
...   def nested(label):
...      global state #用global声明不用一定创建好
...      state = 0  #这里创建state变量
...      print(label,state)
...   return nested
... 
>>> F = tester(0)
>>> F('abc')
abc 0
>>> state
0
  • nonlocal 限制作用域查找仅为嵌套的def,nonlocal不会在嵌套的模块的全局作用域或所有def之外的内置作用域中查找.
>>> spam = 99
>>> def tester():
...   def nested():
...      nonlocal spam #这里出错了,因为在嵌套的本地作用域内没有找到spam变量.
...      print('Current=',spam)
...      spam += 1
...   return nested
... 
  File "<stdin>", line 3
SyntaxError: no binding for nonlocal 'spam' found
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值