• 函数式编程:一等对象、作用域和高阶函数的综合指南


    函数式编程

    在Python中,函数是一等对象。这意味着函数可以像其他对象一样被操作和使用。在函数式编程中,我们可以将函数作为参数传递给其他函数,将函数赋值给变量,甚至将函数作为返回值返回。

    一等对象的特点

    一等对象一般都会具有以下特点:

    1. 对象是在运行时创建的。
    2. 能够赋值给变量或作为数据结构中的元素。
    3. 能够作为参数传递。
    4. 能够作为返回值返回。

    作用域(scope)

    作用域指的是变量生效的区域。

    b = 20 # 全局变量
    
    def fn():
        a = 10 # a定义在了函数内部,所以他的作用域就是函数内部,函数外部无法访问
        print('函数内部:','a =',a)
        print('函数内部:','b =',b)
    
    # fn()    
      
    
    # print('函数外部:','a =',a)
    # print('函数外部:','b =',b)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    在Python中一共有两种作用域:

    全局作用域

    • 全局作用域在程序执行时创建,在程序执行结束时销毁。
    • 所有函数以外的区域都是全局作用域。
    • 在全局作用域中定义的变量,都属于全局变量,全局变量可以在程序的任意位置被访问。

    函数作用域

    • 函数作用域在函数调用时创建,在调用结束时销毁。
    • 函数每调用一次就会产生一个新的函数作用域。
    • 在函数作用域中定义的变量,都是局部变量,它只能在函数内部被访问。

    变量的查找:

    • 当我们使用变量时,会优先在当前作用域中寻找该变量,如果有则使用,如果没有则继续去上一级作用域中寻找,如果有则使用,如果依然没有则继续去上一级作用域中寻找,以此类推,直到找到全局作用域,依然没有找到,则会抛出异常 NameError: name 'a' is not defined
    def fn2():
        def fn3():
            print('fn3中:','a =',a)
        fn3()
    
    # fn2()    
    
    a = 20
    
    def fn3():
        # a = 10 # 在函数中为变量赋值时,默认都是为局部变量赋值
        # 如果希望在函数内部修改全局变量,则需要使用global关键字,来声明变量
        global a # 声明在函数内部的使用a是全局变量,此时再去修改a时,就是在修改全局的a
        a = 10 # 修改全局变量
        print('函数内部:','a =',a)
    
    # fn3()
    # print('函数外部:','a =',a)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    命名空间(namespace)

    命名空间指的是变量存储的位置,每一个变量都需要存储到指定的命名空间当中。每一个作用域都会有一个它对应的命名空间。全局命名空间,用来保存全局变量。函数命名空间用来保存函数中的变量。命名空间实际上就是一个字典,是一个专门用来存储变量的字典。

    # locals()用来获取当前作用域的命名空间
    # 如果在全局作用域中调用locals()则获取全局命名空间,如果在函数作用域中调用locals()则获取函数命名空间
    # 返回的是一个字典
    scope = locals() # 当前命名空间
    print(type(scope))
    # print(a)
    # print(scope['a'])
    # 向scope中添加一个key-value
    scope['c'] = 1000 # 向字典中添加key-value就相当于在全局中创建了一个变量(一般不建议这么做)
    # print(c)
    
    def fn4():
        a = 10
        # scope = locals() # 在函数内部调用locals()会获取到函数的命名空间
        # scope['b'] = 20 # 可以通过scope来操作函数的命名空间,但是也是不建议这么做
    
        # globals() 函数可以用来在任意位置获取全局命名空间
        global_scope = globals()
        # print(global_scope['a'])
        global_scope['a'] = 30
        # print(scope)
    
    fn4()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    练习实操

    • 求阶乘
    • 递归函数
    • 幂运算函数
    • 测试代码

    求阶乘

    # 创建一个函数,可以用来求任意数的阶乘
    def factorial(n):
        '''
            该函数用来求任意数的阶乘
    
            参数:
                n 要求阶乘的数字
        '''
    
        # 创建一个变量,来保存结果
        result = n
        
        for i in range(1,n):
            result *= i
    
        return result    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    递归函数

    # 创建一个函数,用来检查一个任意的字符串是否是回文字符串,如果是返回True,否则返回False
    def hui_wen(s):
        '''
            该函数用来检查指定的字符串是否回文字符串,如果是返回True,否则返回False
    
            参数:
                s:就是要检查的字符串
        '''
    
        # 基线条件
        if len(s) < 2 :
            # 字符串的长度小于2,则字符串一定是回文
            return True
        elif s[0] != s[-1]:
            # 第一个字符和最后一个字符不相等,不是回文字符串
            return False    
        
        # 递归条件    
        return hui_wen(s[1:-1])
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    幂运算函数

    # 创建一个函数 power 来为任意数字做幂运算 n ** i
    def power(n , i):
        '''
            power()用来为任意的数字做幂运算
    
            参数:
                n 要做幂运算的数字
                i 做幂运算的次数
        '''
    
        # 基线条件
        if i == 1:
            # 求1次幂
            return n
        
        # 递归条件
        return n * power(n , i-1)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    测试代码

    # 求10的阶乘    
    print(factorial(10))
    
    # 检查字符串是否回文
    print(hui_wen('abcdefgfedcba'))
    
    # 对10进行5次幂运算
    print(power(10, 5))
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    高阶函数

    高阶函数是指满足以下条件之一的函数:

    1. 接收一个或多个函数作为参数。
    2. 将函数作为返回值返回。

    高阶函数在函数式编程中非常常见。它们使得我们能够更灵活地处理函数,将函数作为数据进行操作和传递。

    接收函数作为参数,或者将函数作为返回值的函数是高阶函数

    当我们使用一个函数作为参数时,实际上是将指定的代码传递进了目标函数

    # 创建一个列表
    l = [1,2,3,4,5,6,7,8,9,10]
    
    # 定义一个函数
    #   可以将指定列表中的所有的偶数,保存到一个新的列表中返回
    
    # 定义一个函数,用来检查一个任意的数字是否是偶数
    def fn2(i) :
        if i % 2 == 0 :
            return True
    
        return False    
    
    # 这个函数用来检查指定的数字是否大于5
    def fn3(i):
        if i > 5 :
            return True    
        return False
    
    def fn(func , lst) :
    
        '''
            fn()函数可以将指定列表中的所有偶数获取出来,并保存到一个新列表中返回
    
            参数:
                lst:要进行筛选的列表
        '''
        # 创建一个新列表
        new_list = []
    
        # 对列表进行筛选
        for n in lst :
            # 判断n的奇偶
            if func(n) :
                new_list.append(n)
            
        # 返回新列表
        return new_list
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38

    将函数作为返回值返回,也是一种高阶函数

    在Python中,我们可以将函数作为返回值返回,这种函数被称为高阶函数。通过高阶函数,我们可以创建一些只有当前函数能访问的变量,这种函数称为闭包。

    def fn():
        a = 10
    
        # 函数内部再定义一个函数
        def inner():
            print('我是fn2', a)
    
        # 将内部函数inner作为返回值返回   
        return inner
    
    # r是一个函数,是调用fn()后返回的函数
    # 这个函数实在fn()内部定义,并不是全局函数
    # 所以这个函数总是能访问到fn()函数内的变量
    r = fn()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    在上面的代码中,我们定义了一个函数fn(),它内部定义了另一个函数inner(),然后将inner()函数作为返回值返回。当我们调用fn()函数后,会得到一个函数对象r,我们可以通过r来调用inner()函数,并且inner()函数可以访问到fn()函数内部的变量a

    求多个数的平均值

    def make_averager():
        nums = []
    
        def averager(n):
            nums.append(n)
            return sum(nums)/len(nums)
    
        return averager
    
    averager = make_averager()
    
    print(averager(10))
    print(averager(20))
    print(averager(30))
    print(averager(40))
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    在上面的代码中,我们定义了一个函数make_averager(),它返回了一个内部函数averager()。通过调用make_averager()函数,我们可以得到一个计算平均值的闭包averager。每次调用averager()函数时,我们将一个数值添加到列表nums中,并返回当前所有数值的平均值。

    我们可以多次调用averager()函数来计算不同数值序列的平均值,由于闭包的特性,它会记住之前的所有数值,从而得到正确的平均值。

    filter()

    filter()可以从序列中过滤出符合条件的元素,保存到一个新的序列中
    参数:

    1. 函数,根据该函数来过滤序列(可迭代的结构)
    2. 需要过滤的序列(可迭代的结构)
      返回值:
      过滤后的新序列(可迭代的结构)
    def fn4(i):
        return i % 3 == 0
            
    r = filter(lambda i : i > 5 , l)
    
    • 1
    • 2
    • 3
    • 4

    map()

    map()函数可以对可迭代对象中的所有元素做指定的操作,然后将其添加到一个新的对象中返回

    l = [1,2,3,4,5,6,7,8,9,10]
    r = map(lambda i : i ** 2 , l)
    
    • 1
    • 2

    sort()

    该方法用来对列表中的元素进行排序
    sort()方法默认是直接比较列表中的元素的大小
    在sort()可以接收一个关键字参数 , key
    key需要一个函数作为参数,当设置了函数作为参数
    每次都会以列表中的一个元素作为参数来调用函数,并且使用函数的返回值来比较元素的大小

    l = ['bb','aaaa','c','ddddddddd','fff']
    # l.sort(key=len)
    
    l = [2,5,'1',3,'6','4']
    l.sort(key=int)
    
    • 1
    • 2
    • 3
    • 4
    • 5

    sorted()

    这个函数和sort()的用法基本一致,但是sorted()可以对任意的序列进行排序
    并且使用sorted()排序不会影响原来的对象,而是返回一个新对象

    l = [2,5,'1',3,'6','4']
    # l = "123765816742634781"
    
    print('排序前:',l)
    print(sorted(l,key=int))
    print('排序后:',l)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    reduce()

    这个方法和map()、filter()不太一样
    reduce()需要导入functools模块

    from functools import reduce
    
    l = [1,2,3,4,5]
    r = reduce(lambda x,y : x + y , l)
    
    • 1
    • 2
    • 3
    • 4

    练习

    使用filter函数找到列表中所有的偶数,使用map函数将每个偶数变成字符串类型,并使用sorted函数进行排序。

    l = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
    
    even_nums = filter(lambda n: n % 2 == 0, l)
    str_nums = map(str, even_nums)
    sorted_nums = sorted(str_nums)
    print(sorted_nums)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    输出:

    ['2', '4', '6', '8', '10']
    
    • 1

    装饰器

    装饰器是一种特殊类型的函数,它可以用于修改或扩展其他函数的功能。装饰器通常使用Python的@语法来应用于目标函数。装饰器可以在不修改原函数代码的情况下,通过包裹原函数来添加额外的行为。

    以下是一个示例装饰器的代码:

    def decorator(func):
        def wrapper(*args, **kwargs):
            print("装饰器添加的额外功能")
            return func(*args, **kwargs)
        return wrapper
    
    @decorator
    def target_function():
        print("目标函数")
    
    target_function()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    输出:

    装饰器添加的额外功能
    目标函数
    
    • 1
    • 2

    在上面的代码中,decorator是一个装饰器函数,它接收一个函数作为参数,并返回一个新的包装函数wrapper。包装函数在调用目标函数之前添加了额外的功能。通过将装饰器应用于target_function,我们可以在调用target_function时获得额外的功能。

    创建几个函数

    def add(a , b):
        '''
            求任意两个数的和
        '''
        r = a + b
        return r
    
    
    def mul(a , b):
        '''
            求任意两个数的积
        '''
        r = a * b
        return r    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    希望函数可以在计算前,打印开始计算,计算结束后打印计算完毕。我们可以直接通过修改函数中的代码来完成这个需求,但是会产生以下一些问题:
    ① 如果要修改的函数过多,修改起来会比较麻烦;
    ② 并且不方便后期的维护;
    ③ 并且这样做会违反开闭原则(OCP),即程序的设计要求开发对程序的扩展,要关闭对程序的修改。

    r = add(123,456)
    print(r)
    
    • 1
    • 2

    我们希望在不修改原函数的情况下,来对函数进行扩展。

    def fn():
        print('我是fn函数....')
    
    # 只需要根据现有的函数,来创建一个新的函数
    def fn2():
        print('函数开始执行~~~')
        fn()
        print('函数执行结束~~~')
    
    fn2() 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    def new_add(a,b):
        print('计算开始~~~')
        r = add(a,b)
        print('计算结束~~~')
        return r
    
    r = new_add(111,222)    
    print(r)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    上边的方式,已经可以在不修改源代码的情况下对函数进行扩展了。但是,这种方式要求我们每扩展一个函数就要手动创建一个新的函数,实在是太麻烦了。为了解决这个问题,我们创建一个函数,让这个函数可以自动的帮助我们生成函数。

    def begin_end(old):
        '''
            用来对其他函数进行扩展,使其他函数可以在执行前打印开始执行,执行后打印执行结束
    
            参数:
                old 要扩展的函数对象
        '''
        # 创建一个新函数
        def new_function(*args , **kwargs):
            print('开始执行~~~~')
            # 调用被扩展的函数
            result = old(*args , **kwargs)
            print('执行结束~~~~')
            # 返回函数的执行结果
            return result
    
        # 返回新函数        
        return new_function
    
    f = begin_end(fn)
    f2 = begin_end(add)
    f3 = begin_end(mul)
    
    r = f()
    r = f2(123,456)
    r = f3(123,456)
    print(r)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    begin_end()这种函数我们就称它为装饰器。通过装饰器,可以在不修改原来函数的情况下来对函数进行扩展。在开发中,我们都是通过装饰器来扩展函数的功能的。

    在定义函数时,可以通过@装饰器,来使用指定的装饰器,来装饰当前的函数。可以同时为一个函数指定多个装饰器,这样函数将会按照从内向外的顺序被装饰。

    def fn3(old):
        '''
            用来对其他函数进行扩展,使其他函数可以在执行前打印开始执行,执行后打印执行结束
    
            参数:
                old 要扩展的函数对象
        '''
        # 创建一个新函数
        def new_function(*args , **kwargs):
            print('fn3装饰~开始执行~~~~')
            # 调用被扩展的函数
            result = old(*args , **kwargs)
            print('fn3装饰~执行结束~~~~')
            # 返回函数的执行结果
            return result
    
        # 返回新函数        
        return new_function
    
    @fn3
    @begin_end
    def say_hello():
        print('大家好~~~')
    
    say_hello()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    总结

    函数式编程是一种编程范式,它将计算过程视为数学函数的组合。在函数式编程中,函数被视为一等对象,具有以下特点:

    1. 作为参数传递:函数可以作为参数传递给其他函数,以实现更灵活的功能。
    2. 作为返回值返回:函数可以作为另一个函数的返回值,以实现可定制的行为。
    3. 可以赋值给变量:函数可以被赋值给变量,以便进一步使用。

    作用域是指在程序中定义变量的区域,它决定了变量的可见性和生命周期。常见的作用域包括全局作用域和函数作用域。

    全局作用域是在整个程序中都可以访问的作用域,而函数作用域只在函数内部可见。

    命名空间是一个用于存储变量和函数名称的容器。它提供了一种将名称与特定作用域中的对象关联起来的方式。

    在实操练习中,使用函数式编程的概念来实现了以下功能:

    1. 求阶乘:通过递归函数实现了求阶乘的功能。
    2. 幂运算函数:通过高阶函数实现了对一个数进行幂运算的功能。
    3. 求多个数的平均值:通过高阶函数和reduce()函数实现了求多个数的平均值的功能。

    装饰器是一种用于修改已有函数行为的函数。它可以在不修改原函数代码的情况下,给函数添加新的功能。在实操练习中,使用装饰器实现了对函数进行扩展的功能。

    总结:函数式编程通过将函数视为一等对象,使得程序更具灵活性和可扩展性。它包括了一等对象、作用域、命名空间等概念,并可以通过高阶函数、递归函数、装饰器等实现各种功能。函数式编程是一种强大的编程范式,能够简化复杂问题的解决过程。

  • 相关阅读:
    android Google官网 :支持不同的语言和文化 rtl / ltr : 本地化适配:RTL(right-to-left) 适配
    ubuntu20.0安装 java并配置环境
    河南短视频代运营|短视频代运营能不能纯佣金合作
    关于Validation的方法使用
    应用层Buffer缓冲区及其重要性
    用DIV+CSS技术设计的音乐主题网站(web前端网页制作课作业)
    Transformer发展历程 Decoder输出特征利用DCT解码投分割
    Flutter中set和get方法
    mongodb 在windows下 安装及使用
    HTML笔记
  • 原文地址:https://blog.csdn.net/qq_41308872/article/details/132838449