博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
python 变量作用域、闭包、装饰器
阅读量:4203 次
发布时间:2019-05-26

本文共 4042 字,大约阅读时间需要 13 分钟。

一.python的作用域

1,什么是“LEGB”

Python与大多数编程语言一样,搜索变量值的时候,即命名空间的规则,会采用’就近原则’:

由近及远依次为: 本地作用域(Local) --> 外部嵌套函数作用域(Enclosing Local) --> 全局/模块作用域(Global) --> 内置作用域(Built-in).

a = str(1) # str:Built-inb = 3       # b:Globaldef f1():    print(b) # b:Global    a = 3    # a:Local    print(a)def f2():    a = 3         # a:Enclosing    def f3():        b = 3     # b:Local        print(b)
  • LocalEnclosing 是一个相对的概念. 在函数 f1 中, a 是一个 Local 变量,而在 f2 中 a 是一个 Enclosing 变量。
  • 只有模块、类、函数才会引入新的作用域. 而 if for while 语句不会引入新的作用域.
  • 全局作用域中的变量对于下层作用域比如函数来说, 是一个只读变量.
2,使用global、nonlocal 关键字声明外部作用域变量
1)示例:

示例1:

a = 4def foo():    a = 3foo()print(a) # 4

示例2:

a = 4def foo():    a = a + 3foo()# local variable 'a' referenced before assignment
  • 上面的示例1中,我们可以看到,因为全局变量对于下层的作用域来说只有只读权限,所以函数内部不是修改全局变量 a 的值,而是重新定义了一个本地变量 a。所以全局变量 a 的值并没有改变。
  • 上面的示例2中,还是由于局变量对于下层的作用域来说只有只读权限,在函数 foo 内部, a = a + 3 这个表达式的存在会让 Python 编译函数的定义体时,它判断 a 是局部变量,因为在函数中给它赋值了。所以才出现为变量未定义的报错。
2)global、nonlocal

如何在内部作用域中去修改外部作用域的变量值呢?这里推出两个关键字来声明外部作用域变量:globalnonlocal 。内部作用域中要修改外部作用域变量的值时。要用 global、nonlocal 关键字声明外部作用域变量。

a = 3def foo():    global a  # 使用 global 声明 a ,便可以在函数中修改 a 的值    a = 4foo()print(a) # 4       def f1():    a = 3    def f2():        nonlocal a  # 使用 nonlocal 声明 a, 便可以在嵌套的函数内部修改 a 的值        a = a + 1        print(a)    f2()
  • 如上示例所示,关键字global用于本地作用域声明全局/模块作用域变量使用;而关键字nonlocal用于本地作用域声明外部嵌套函数作用域变量使用。

二,闭包与自由变量

1)函数实质与属性
  1. 函数是一个对象
  2. 函数执行后完成内部变量回收
  3. 函数属性
  4. 函数返回值
def check(val):    #以16进制打印val的id值    print('%x'%id(val))    if val > 60:        print('pass')    else:        print('fail')    def func():        print(val)    func()    return funcif __name__ == "__main__":    f = check(90)    f()    print(f.__closure__)	# closure:内部函数中对enclosing作用域的变量进行引用    ---------output-------------------557a7165b5e0  # 此时可看出val的id值为1009dd4a0pass90---------90---------(
,)

按照python的变量回收机制,val在check函数执行完成后,应该被回收掉,为什么会出现调用f()时,出现val的值呢?f中存在int类型的val值的引用,因为python采用引用计数的垃圾回收机制,当check()函数执行完成后,val对象被保存在func的函数属性中,仍存在引用,所以并没有对val进行垃圾回收。

2)什么是闭包、自由变量

什么是闭包:

闭包指延伸了作用域的函数,其中包含函数定义体中引用、但是不在定义体中定义的非全局变量。函数是不是匿名的没有关系,关键是它能访问定义体之外定义的非全局变量

总结:闭包实质上是一个函数,它能够访问函数体之外定义的非全局变量,而这个非全局变量指的就是自由变量。它使得函数在执行后,函数的返回对象不会被内存回收,并且闭包中包含着自由变量,只要闭包能被访问到,自由变量就可以被访问。

示例1:

下面举一个例子, 定义一个 avg 函数,参数为一个值, 不断累加的计算从开始到现在所接收的全部值的平均值

def make_average():    series = []    def average(value):        series.append(value)        total = sum(series)        return total / len(series)    return averageavg = make_average() # 1print(avg(10))      # 2print(avg(20))      # 3#-----output----------10.015.0
  • 调用 make_average 返回一个 average 函数对象.这就是一个闭包函数,因为 avg 可以访问 average 函数定义体之外的 series。
  • 这里嵌套函数 average 并没有’改变’ series, 只是修改它的值, 因为 series 是一个可变的列表.所以并不会报错.那么如果 series 是一个不可变对象呢? 会发生什么?

上面的例子效率比较低, 没一次都得 sum.我们难道不可以保存每一步计算的 total 吗?

示例2:

def make_average():    count = 0    total = 0    def average(value):        count += 1        total += value        return total / count    return averageavg = make_average()avg(10)# UnboundLocalError: local variable 'count' referenced before assignment
  • 说明: 在嵌套函数 average 内部有 count += 1,此表达式等价为 count = count + 1。在编译阶段,会把内部的 count 解释为一个本地变量,所以如果没有 nonlocal 声明的话,会报错 local variable ‘count’ referenced before assignment

示例3:

我们需要使用 nonlocal 将 count 和 total 变成自由变量

def make_average():    count = 0    total = 0    def average(value):        nonlocal count, total        count += 1        total += value        return total / count    return average# 开始调用.返回一个函数对象,并且这个对象是一个闭包avg = make_average()print(avg(10))print(avg(20))#-----output-----------------------10.015.0

三,装饰器

装饰器用来’装饰’一个函数,为函数添加额外的功能。

装饰器接收一个函数作为参数,装饰器可能会处理被装饰的函数,然后把它返回,或者将其替换成另一个函数或可调用对象。
举例, 我们定义一个装饰器,用来计算并且显示每个函数运行的时间。

import timedef decorate(func):    def wrapper(*args):        to = time.perf_counter()        result = func(*args)        t1 = time.perf_counter() - to        print(f'运行时间为:{t1}')        return result    return wrapper
@decoratedef foo(n):    i = 0    while i < n*n:        i += 1    return iprint(foo(600))# 程序的运行时间为:0.022877089999383315# 360000

@decorate是一个语法糖, 等同于 func = decorate(func), 所以此时 func 是 wrapper 函数的引用.

转载地址:http://bsili.baihongyu.com/

你可能感兴趣的文章
Redis简介
查看>>
技术管理
查看>>
高并发
查看>>
MySQL常用设置
查看>>
Linux 运维常用网络命令
查看>>
JavaEE常用框架汇总
查看>>
分布式数据库汇总
查看>>
Vim 命令
查看>>
Flink
查看>>
NTP-网络时间协议
查看>>
C/C++学习方法
查看>>
Borland编译器,在windows7的命令行中运行C++
查看>>
Apache Derby 网络服务器 - 10.9.1.0 - (1344872) 已启动并准备接受端口 1527 上的连接
查看>>
Java日常常用小算法
查看>>
JavaSE经典编程示例
查看>>
Eclipse软件相关知识
查看>>
人工智能资料汇总--AI传送门
查看>>
百度地图SDKv4.1.1 错误码230
查看>>
Android百度地图SDK -- 环境搭建
查看>>
Android学习路线
查看>>