Python函数作用域嵌套函数闭包函数高阶函数及装饰器的理解

Posted 尼古拉斯&特仑苏

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Python函数作用域嵌套函数闭包函数高阶函数及装饰器的理解相关的知识,希望对你有一定的参考价值。

一、前言

初次学习Python过程中,闭包、高阶函数、函数嵌套及装饰器这些概念以及他们之间的关系一时半会难以理解,这些是Python函数的精华,需要花点时间好好领悟。其中装饰器是最难理解的,为什么呢?因为只有把变量作用域、闭包函数、嵌套函数、高阶函数都理解了,装饰器才能更容易理解一些。他们是成一个体系的知识,必须要成体系的学习。下面我们就循序渐进、一层一层剥离,最终一定会理解好以上所有的概念。

二、嵌套函数

说起嵌套函数肯定有人要抢答说:不就是函数里边调用函数吗?像这样:

def func1():
    print ‘func1‘

def func2():
    print ‘func2‘
    func1()
func2()

真正的函数嵌套应该,在一个函数中又定义了一个函数。

def outer():
	 print \'outer\'
    def inner():
       print \'inner\'

outer()

像这种在一个函数体内又创建了一个函数,这种形式才属于函数嵌套。

三、函数作用域

1、作用域分类

整明白闭包之前需要先了解一下变量的作用域,变量按作用域一般分为两类,全局作用域和局部作用域,但是再深层次分,变量的作用域从外到里依次是:

L(local) 局部作用域        
E(Enclosing) 闭包函数外的函数中     
G(Global) 全局作用域    
B(Built-in) 内建作用域 

遵循LEGB原则:以 L –> E –> G –>B 的规则查找,即:在局部找不到,便会去局部外的嵌套作用域找(例如闭包),再找不到就会去全局找,再者去内建中找。什么意思?举例说明:

a =int(\'3\') #内建作用域
a = 2		  #全局作用域
def outer():
    b = 3   #嵌套作用域(闭包数外外部函数中的作用域)

    def inner(): 
        c = 3   #局部作用域
        return \'inner\'

    return inner()

outer()
2、什么情况下会产生新的作用域

在python中,模块(module)、类(class)、函数(def、lambda)会产生新的作用域,其他代码块是不会产生作用域的,也就是说,类似条件判断(if…..else)、循环语句(for x in data)、异常捕捉(try…catch)等的变量是可以全局使用的

dataList = [1, 2, 3, 4]
for data in dataList:
    a = 1   #for循环中的变量a
    b = data + a

print(a) #在函数外也可视为全局变量使用
3、变量作用域注意点

1、局部变量无法修改全局变量,除非在全局变量上加上global

(1)未使用global

a = 1
def demo():
    # IDE提示a = 123:This inspection detects shadowing names defined in outer scopes
    # 大概意思是给变量取的这个名字,可能会冲突,它是函数外部的变量
    a = 123
    print(a)

demo()
print(a)


运行结果是

123
1
全局变量a的值还是1,没有被改变

(2)使用global

a = 1
def demo():
    global a
    a = 123
    print(a)
    
demo()
print(a)

运行结果是
123
123
全局变量a的值被修改

2、局部变量无法修改嵌套变量,除非在外部变量上加上nonlocal,nonlocal 关键字的使用方法和global关键字类似,修改嵌套作用域(enclosing 作用域,外层非全局作用域)中的变量
(1)未使用nonlocal

def outer():
    num = 10
    def inner():
        num = 100
        print(num)
    inner()
    print(num)
outer()

运行结果为
100
10
闭包函数外的变量num值未被修改

(2)使用nonlocal

def outer():
    num = 10
    def inner():
        nonlocal num   # nonlocal关键字声明
        num = 100
        print(num)
    inner()
    print(num)
outer()

运行结果为
100
100
闭包函数外的变量num值被修改

闭包函数

闭包函数定义

闭包函数可以这样理解:在一个内部函数中,局部变量对外部作用域(这里的外部作用域指的不是全局作用域!而是嵌套作用域)的变量进行引用,函数内部就可以被理解为是闭包的。这个内部函数就是闭包函数。

def outer():
    num = 10
    def inner():
        print(num)
   
    print(num)
outer()
闭包函数需要注意点:

1、闭包函数内部,默认是不可以修改外部作用域的变量的!!(使用nonlocal关键字声明例外)

这样IDE就会报下面的错误:

2、在闭包函数中定义的变量a,并没有影响到外部作用域的a变量。

def outer():
    num = 10
    def inner():
        num = 100
        print(num)
    inner()
    print(num)
outer()

运行结果为
100
10

高阶函数

什么是高阶函数

满足以下两个条件其中之一的均为高阶函数:

1、另外一个函数作为本函数的参数
2、本函数返回值是一个函数

def test1():

    print "allen" 

def test2(func):

    return test1

装饰器

最后的大Boss终于出现了,如果以上概念都理解了,装饰器其实很简单。装饰器也是一个函数,只不过这个函数比较特殊。当然装饰器也可以是一个类,下面我们会聊到。首先我们要知道,python为什么会有装饰器这个概念,而像java就没有这个概念,那是因为python的函数可以作为一个变量被引用 ,这句话两层含义:
1、函数可以当做另外一个函数的参数
2、函数可以当做另外一个函数的返回值

下面我们通过一个故事《python装饰器诞生记》看看什么是装饰器?

假如小明同学刚学不就python到公司就完成了很多需求,下面是他完成其中一个需求而写的一个函数:

import time
def foo():
	 print(\'foo run finish.......\')
	time.sleep(3)

然后需求来了,要求小明同学计算所有函数执行的时间,小明微微一笑,这还不简单,欣然答应。于是吭哧吭哧大改一通。老大过了一段时间,问小明需求实现了没有,小明屁颠屁颠拿出了代码:

def foo():
    start = time.time()
    print(\'foo run finish.......\')
    time.sleep(3)
    end = time.time()
    print(\'time long %s\' % (end - start))

foo()

...
...
...
...
五百个函数同上

老大看了之后,知道小明是新手,也没有批评他,就给小明说,为什么不用聪明一点的办法,让小明拿回去自己想如何重做。小明同学有重写了一个版本:

def foo():
    print(\'foo run ok....\')
    time.sleep(3)


def comp_time(func):
    start = time.time()
    func()
    end = time.time()
    print(\'time long %s\' % (end - start))

comp_time(foo)

领导看了之后有进步了,但还是不够好,本来要调用foo()完成一个功能,修改后是调用comp_time()完,修改了原有的调用方式,让小明继续改。小明有出了一个版本:

def foo():
    print(\'foo run ok....\')
    time.sleep(3)


def comp_time(func):
    def print_time():
        start = time.time()
        func()
        end = time.time()
        print(\'time long %s\' % (end - start))
        return end - start

    return print_time


foo = comp_time(foo)
foo()

领导看了后感觉小明还是有两下子的,但是还没达到预期,又让小明再改,小明这下课难住了,尼玛,还怎么改?不过不气馁的小明,翻阅了python从入门到放弃宝典之后,又出了一个版本:

def comp_time(func):
    def print_time():
        start = time.time()
        func()
        end = time.time()
        print(\'time long %s\' % (end - start))
        return end - start

    return print_time


@comp_time
def foo():
    print(\'foo run ok....\')
    time.sleep(3)
foo()

老板看了后,拍手叫好,对小明说:小伙子不错嘛,干的漂亮,好好犒劳一下你,晚上留下来继续加班

故事结束了

我们的装饰器也诞生了,装饰器就是一个在不改变原有函数内容和调用方式的基础上帮助此函数进行功能上拓展的一个函数或者类。说白了闭包函数+高阶函数+嵌套函数+@语法糖=装饰器

装饰器结合故事中最后一个版本代码,foo()的调用方式和内容并没有改变,只是在原函数基础用了一个语法糖 @comp_time,comp_time()函数帮助foo()函数拓展了计算执行时间的功能。comp_time()函数就是一个装饰器。如果到这你还没看懂什么是装饰器,那就从头再看一遍,如果一遍不行就两边,之后再看不懂,我也没办法了。

下面看一下被装饰函数如何带参数:

def comp_time(func):
    def print_time(name):
        start = time.time()
        func(name)
        end = time.time()
        print(\'%s time long %s\' % (name,end - start))
        return end - start

    return print_time


@comp_time
def foo(name):
    print(\'foo run ok....\')
    time.sleep(3)
foo()

需要在被装饰函数、装饰器内部函数上加上形参、在调用被装饰函数的地方传入实参

装饰器如何带参数呢:

def user_log(name):
    def comp_time(func):
        def print_time():
          	start = time.time()
        		func()
        		end = time.time()
        		print(\'time long %s\' % (end - start))
        		return end - start
        return print_time
   
    return comp_time


@comp_time(name)
def foo():
    print(\'foo run ok....\')
    time.sleep(3)
foo()

上边说到装饰器也可以是一个类:

class Foo(object):
    def __init__(self, func):
        self._func = func

    def __call__(self):
        print(\'class decorator runing\')
        self._func()
        print(\'class decorator ending\')


@Foo
def bar():
    print(\'bar\')

bar()

到这里装饰器的内容就说完了

以上是关于Python函数作用域嵌套函数闭包函数高阶函数及装饰器的理解的主要内容,如果未能解决你的问题,请参考以下文章

python函数下篇装饰器和闭包,外加作用域

python笔记--作用域高阶函数闭包

python11 装饰器与闭包

013.Python之函数嵌套名称空间与作用域闭包函数

JavaScript语言核心-- 高阶函数 及 闭包

Python中的闭包