【从零学习python 】31.深入理解Python中的高阶函数和闭包

简介: 【从零学习python 】31.深入理解Python中的高阶函数和闭包

高阶函数

在Python中,函数其实也是一种数据类型

def test():
    return 'hello world'
print(type(test))  # <class 'function'>

函数对应的数据类型是 function,可以把它当做是一种复杂的数据类型。

既然同样都是一种数据类型,我们就可以把它当做数字或者字符串来处理。

定义一个变量指向函数

在Python中,我们还可以定义一个变量,让它来指向一个函数,相当于给函数起了一个别名。

def test():
    return 'hello wrold'
fun = test   # 定义了一个变量fun,让它指向了 test 这个函数
print(fun())   # 使用fun()可以直接调用test这个函数
print(id(fun))  # 1819677672040
print(id(test))  # 1819677672040

注意:在定义一个变量表示一个函数时,函数后面不能加括号!加括号表示的是调用这个函数。

def test():
    return 'hello world'
result = test()   # 这种写法是调用test函数,并把函数的返回值赋值给result变量
print(result())   # 这里会报错  TypeError: 'str' object is not callable
fun = test   # 这种写法是给test函数起了一个别名,注意,这里的test后面不能加()
fun()        # 可以使用别名调用这个函数

高阶函数

既然变量可以指向函数,函数的参数能接收变量,那么一个函数就可以接收另一个函数作为参数,同样,我们还可以把一个函数当做另一个函数的返回值。这种函数的使用方式我们称之为高阶函数。

函数做为另一个函数的参数

def test(age, action):
    if age < 18:
        print('您还没满十八岁,请退出')
    action()   # 把参数action直接当做一个函数来调用
def smoke():
    print('我已经年满十八岁了,我想抽烟')
my_action = smoke  # 定义一个变量my_action,让它指向smoke函数
test(21, my_action)  # 将my_action传给test函数作为它的参数
test(21, smoke)  # 还可以不再定义一个新的变量,直接传入函数名

函数作为另一个函数的返回值

def test():
    print('我是test函数里输入的内容')
def demo():
    print('我是demo里输入的内容')
    return test  # test 函数作为demo函数的返回值
result = demo()  # 我是demo里输入的内容  调用 demo 函数,把demo函数的返回值赋值给 result
print(type(result)) # <class 'function'>  result 的类型是一个函数
result() # 我是demo里输入的内容    我是test函数里输入的内容   既然result是一个函数,那么就可以直接使用() 调用这个函数
demo()()  # 我是demo里输入的内容    我是test函数里输入的内容

闭包

函数只是一段可执行代码,编译后就“固化”了,每个函数在内存中只有一份实例,得到函数的入口点便可以执行函数了。函数还可以嵌套定义,即在一个函数内部可以定义另一个函数,有了嵌套函数这种结构,便会产生闭包问题。

函数嵌套

在函数里面还可以定义函数,可以嵌套多层,执行需要被调用。

def outer():
    print('outer----hello')
    def inner():  # inner这个函数是在outer函数内部定义的
        print('inner----hello')
    inner()  # inner函数只在outer函数内部可见
outer()
# inner()  这里会报错,在outer函数外部无法访问到inner函数

什么是闭包

闭包是由函数及其相关的引用环境组合而成的实体(即:闭包=函数块+引用环境)。

def outer(n):
    num = n
    def inner():
        return num+1
    return inner
print(outer(3)())  # 4
print(outer(5)())  # 5

在这段程序中,函数 inner 是函数 outer 的内嵌函数,并且 inner 函数是outer函数的返回值。我们注意到一个问题:内嵌函数 inner 中引用到外层函数中的局部变量num,Python解释器会这么处理这个问题呢?先让我们来看看这段代码的运行结果,当我们调用分别由不同的参数调用 outer 函数得到的函数时,得到的结果是隔离的(相互不影响),也就是说每次调用outer函数后都将生成并保存一个新的局部变量num,这里outer函数返回的就是闭包。如果在一个内部函数里,对在外部作用域(但不是在全局作用域)的变量进行引用,那么内部函数就被认为是闭包(closure)。

修改外部变量的值

闭包里默认不能修改外部变量。

def outer(n):
    num = n
    def inner():
        num = num + 1
        return num
    return inner
print(outer(1)())

上述代码运行时会报错!

UnboundLocalError: local variable 'num' referenced before assignment

原因分析

在python里,只要看到了赋值语句,就会认为赋值语句的左边是一个局部变量。num = num + 1 这段代码里,num=的左边,python解析器会认为我们要修改inner函数里num这个局部变量,而这个变量使用之前是未声明的,所以会报错。

解决方案

我们分析过,报错的原因在于当我们在闭包内修改外部变量时,会被python解析器误会为内部函数的局部变量。所以,解决方案就在于,我们需要想办法,让解析器知道我们不是要修改局部变量,而是要修改外部变量。

解决方法:使用 nonlocal 关键字

def outer(n):
    num = n
    def inner():
        nonlocal num  # 修改前使用nonlocal关键字对 num 变量进行说明
        num = num + 1
        return num
    return inner
print(outer(2)())
相关文章
|
10天前
|
Python 容器
Python学习的自我理解和想法(9)
这是我在B站跟随千锋教育学习Python的第9天,主要学习了赋值、浅拷贝和深拷贝的概念及其底层逻辑。由于开学时间紧张,内容较为简略,但希望能帮助理解这些重要概念。赋值是创建引用,浅拷贝创建新容器但元素仍引用原对象,深拷贝则创建完全独立的新对象。希望对大家有所帮助,欢迎讨论。
|
1天前
|
Python
Python学习的自我理解和想法(10)
这是我在千锋教育B站课程学习Python的第10天笔记,主要学习了函数的相关知识。内容包括函数的定义、组成、命名、参数分类(必须参数、关键字参数、默认参数、不定长参数)及调用注意事项。由于开学时间有限,记录较为简略,望谅解。通过学习,我理解了函数可以封装常用功能,简化代码并便于维护。若有不当之处,欢迎指正。
|
12天前
|
存储 索引 Python
Python学习的自我理解和想法(6)
这是我在B站千锋教育学习Python的第6天笔记,主要学习了字典的使用方法,包括字典的基本概念、访问、修改、添加、删除元素,以及获取字典信息、遍历字典和合并字典等内容。开学后时间有限,内容较为简略,敬请谅解。
|
16天前
|
存储 程序员 Python
Python学习的自我理解和想法(2)
今日学习Python第二天,重点掌握字符串操作。内容涵盖字符串介绍、切片、长度统计、子串计数、大小写转换及查找位置等。通过B站黑马程序员课程跟随老师实践,非原创代码,旨在巩固基础知识与技能。
|
15天前
|
程序员 Python
Python学习的自我理解和想法(3)
这是学习Python第三天的内容总结,主要围绕字符串操作展开,包括字符串的提取、分割、合并、替换、判断、编码及格式化输出等,通过B站黑马程序员课程跟随老师实践,非原创代码。
|
12天前
|
Python
Python学习的自我理解和想法(7)
学的是b站的课程(千锋教育),跟老师写程序,不是自创的代码! 今天是学Python的第七天,学的内容是集合。开学了,时间不多,写得不多,见谅。
|
11天前
|
存储 安全 索引
Python学习的自我理解和想法(8)
这是我在B站千锋教育学习Python的第8天,主要内容是元组。元组是一种不可变的序列数据类型,用于存储一组有序的元素。本文介绍了元组的基本操作,包括创建、访问、合并、切片、遍历等,并总结了元组的主要特点,如不可变性、有序性和可作为字典的键。由于开学时间紧张,内容较为简略,望见谅。
|
12天前
|
存储 索引 Python
Python学习的自我理解和想法(4)
今天是学习Python的第四天,主要学习了列表。列表是一种可变序列类型,可以存储任意类型的元素,支持索引和切片操作,并且有丰富的内置方法。主要内容包括列表的入门、关键要点、遍历、合并、判断元素是否存在、切片、添加和删除元素等。通过这些知识点,可以更好地理解和应用列表这一强大的数据结构。
|
12天前
|
索引 Python
Python学习的自我理解和想法(5)
这是我在B站千锋教育学习Python的第五天笔记,主要内容包括列表的操作,如排序(`sort()`、``sorted()``)、翻转(`reverse()`)、获取长度(`len()`)、最大最小值(`max()`、``min()``)、索引(`index()`)、嵌套列表和列表生成(`range`、列表生成式)。通过这些操作,可以更高效地处理数据。希望对大家有所帮助!
|
18天前
|
安全 程序员 Python
Python学习的自我理解和想法(1)
本篇博客记录了作者跟随B站“黑马程序员”课程学习Python的第一天心得,涵盖了`print()`、`input()`、`if...else`语句、三目运算符以及`for`和`while`循环的基础知识。通过实际编写代码,作者逐步理解并掌握了这些基本概念,为后续深入学习打下了良好基础。文中还特别强调了循环语句的重要性及其应用技巧。