函数式编程
在Python中,函数是一等对象。这意味着函数可以像其他对象一样被操作和使用。在函数式编程中,我们可以将函数作为参数传递给其他函数,将函数赋值给变量,甚至将函数作为返回值返回。
一等对象的特点
一等对象一般都会具有以下特点:
- 对象是在运行时创建的。
- 能够赋值给变量或作为数据结构中的元素。
- 能够作为参数传递。
- 能够作为返回值返回。
作用域(scope)
作用域指的是变量生效的区域。
b = 20 # 全局变量 def fn(): a = 10 # a定义在了函数内部,所以他的作用域就是函数内部,函数外部无法访问 print('函数内部:','a =',a) print('函数内部:','b =',b) # fn() # print('函数外部:','a =',a) # print('函数外部:','b =',b)
在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)
命名空间(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()
练习实操
- 求阶乘
- 递归函数
- 幂运算函数
- 测试代码
求阶乘
# 创建一个函数,可以用来求任意数的阶乘 def factorial(n): ''' 该函数用来求任意数的阶乘 参数: n 要求阶乘的数字 ''' # 创建一个变量,来保存结果 result = n for i in range(1,n): result *= i return result
递归函数
# 创建一个函数,用来检查一个任意的字符串是否是回文字符串,如果是返回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])
幂运算函数
# 创建一个函数 power 来为任意数字做幂运算 n ** i def power(n , i): ''' power()用来为任意的数字做幂运算 参数: n 要做幂运算的数字 i 做幂运算的次数 ''' # 基线条件 if i == 1: # 求1次幂 return n # 递归条件 return n * power(n , i-1)
测试代码
# 求10的阶乘 print(factorial(10)) # 检查字符串是否回文 print(hui_wen('abcdefgfedcba')) # 对10进行5次幂运算 print(power(10, 5))
高阶函数
高阶函数是指满足以下条件之一的函数:
- 接收一个或多个函数作为参数。
- 将函数作为返回值返回。
高阶函数在函数式编程中非常常见。它们使得我们能够更灵活地处理函数,将函数作为数据进行操作和传递。
接收函数作为参数,或者将函数作为返回值的函数是高阶函数
当我们使用一个函数作为参数时,实际上是将指定的代码传递进了目标函数
# 创建一个列表 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
将函数作为返回值返回,也是一种高阶函数
在Python中,我们可以将函数作为返回值返回,这种函数被称为高阶函数。通过高阶函数,我们可以创建一些只有当前函数能访问的变量,这种函数称为闭包。
def fn(): a = 10 # 函数内部再定义一个函数 def inner(): print('我是fn2', a) # 将内部函数inner作为返回值返回 return inner # r是一个函数,是调用fn()后返回的函数 # 这个函数实在fn()内部定义,并不是全局函数 # 所以这个函数总是能访问到fn()函数内的变量 r = fn()
在上面的代码中,我们定义了一个函数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))
在上面的代码中,我们定义了一个函数make_averager()
,它返回了一个内部函数averager()
。通过调用make_averager()
函数,我们可以得到一个计算平均值的闭包averager
。每次调用averager()
函数时,我们将一个数值添加到列表nums
中,并返回当前所有数值的平均值。
我们可以多次调用averager()
函数来计算不同数值序列的平均值,由于闭包的特性,它会记住之前的所有数值,从而得到正确的平均值。
filter()
filter()可以从序列中过滤出符合条件的元素,保存到一个新的序列中
参数:
- 函数,根据该函数来过滤序列(可迭代的结构)
- 需要过滤的序列(可迭代的结构)
返回值:
过滤后的新序列(可迭代的结构)
def fn4(i): return i % 3 == 0 r = filter(lambda i : i > 5 , l)
map()
map()函数可以对可迭代对象中的所有元素做指定的操作,然后将其添加到一个新的对象中返回
l = [1,2,3,4,5,6,7,8,9,10] r = map(lambda i : i ** 2 , l)
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)
sorted()
这个函数和sort()的用法基本一致,但是sorted()可以对任意的序列进行排序
并且使用sorted()排序不会影响原来的对象,而是返回一个新对象
l = [2,5,'1',3,'6','4'] # l = "123765816742634781" print('排序前:',l) print(sorted(l,key=int)) print('排序后:',l)
reduce()
这个方法和map()、filter()不太一样
reduce()需要导入functools模块
from functools import reduce l = [1,2,3,4,5] r = reduce(lambda x,y : x + y , l)
练习
使用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)
输出:
['2', '4', '6', '8', '10']
装饰器
装饰器是一种特殊类型的函数,它可以用于修改或扩展其他函数的功能。装饰器通常使用Python的@
语法来应用于目标函数。装饰器可以在不修改原函数代码的情况下,通过包裹原函数来添加额外的行为。
以下是一个示例装饰器的代码:
def decorator(func): def wrapper(*args, **kwargs): print("装饰器添加的额外功能") return func(*args, **kwargs) return wrapper @decorator def target_function(): print("目标函数") target_function()
输出:
装饰器添加的额外功能 目标函数
在上面的代码中,decorator
是一个装饰器函数,它接收一个函数作为参数,并返回一个新的包装函数wrapper
。包装函数在调用目标函数之前添加了额外的功能。通过将装饰器应用于target_function
,我们可以在调用target_function
时获得额外的功能。
创建几个函数
def add(a , b): ''' 求任意两个数的和 ''' r = a + b return r def mul(a , b): ''' 求任意两个数的积 ''' r = a * b return r
希望函数可以在计算前,打印开始计算,计算结束后打印计算完毕。我们可以直接通过修改函数中的代码来完成这个需求,但是会产生以下一些问题:
① 如果要修改的函数过多,修改起来会比较麻烦;
② 并且不方便后期的维护;
③ 并且这样做会违反开闭原则(OCP),即程序的设计要求开发对程序的扩展,要关闭对程序的修改。
r = add(123,456) print(r)
我们希望在不修改原函数的情况下,来对函数进行扩展。
def fn(): print('我是fn函数....') # 只需要根据现有的函数,来创建一个新的函数 def fn2(): print('函数开始执行~~~') fn() print('函数执行结束~~~') fn2()
def new_add(a,b): print('计算开始~~~') r = add(a,b) print('计算结束~~~') return r r = new_add(111,222) print(r)
上边的方式,已经可以在不修改源代码的情况下对函数进行扩展了。但是,这种方式要求我们每扩展一个函数就要手动创建一个新的函数,实在是太麻烦了。为了解决这个问题,我们创建一个函数,让这个函数可以自动的帮助我们生成函数。
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)
向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()
总结
函数式编程是一种编程范式,它将计算过程视为数学函数的组合。在函数式编程中,函数被视为一等对象,具有以下特点:
- 作为参数传递:函数可以作为参数传递给其他函数,以实现更灵活的功能。
- 作为返回值返回:函数可以作为另一个函数的返回值,以实现可定制的行为。
- 可以赋值给变量:函数可以被赋值给变量,以便进一步使用。
作用域是指在程序中定义变量的区域,它决定了变量的可见性和生命周期。常见的作用域包括全局作用域和函数作用域。
全局作用域是在整个程序中都可以访问的作用域,而函数作用域只在函数内部可见。
命名空间是一个用于存储变量和函数名称的容器。它提供了一种将名称与特定作用域中的对象关联起来的方式。
在实操练习中,使用函数式编程的概念来实现了以下功能:
- 求阶乘:通过递归函数实现了求阶乘的功能。
- 幂运算函数:通过高阶函数实现了对一个数进行幂运算的功能。
- 求多个数的平均值:通过高阶函数和reduce()函数实现了求多个数的平均值的功能。
装饰器是一种用于修改已有函数行为的函数。它可以在不修改原函数代码的情况下,给函数添加新的功能。在实操练习中,使用装饰器实现了对函数进行扩展的功能。
总结:函数式编程通过将函数视为一等对象,使得程序更具灵活性和可扩展性。它包括了一等对象、作用域、命名空间等概念,并可以通过高阶函数、递归函数、装饰器等实现各种功能。函数式编程是一种强大的编程范式,能够简化复杂问题的解决过程。