函数介绍
- 函数式编程
- 一等对象的特点
- 作用域(scope)
- 全局作用域
- 函数作用域
- 命名空间(namespace)
- 练习实操
- 求阶乘
- 递归函数
- 幂运算函数
- 测试代码
- 高阶函数
- 接收函数作为参数,或者将函数作为返回值的函数是高阶函数
- 将函数作为返回值返回,也是一种高阶函数
- 求多个数的平均值
- filter()
- map()
- sort()
- sorted()
- reduce()
- 练习
- 装饰器
- 创建几个函数
- 总结
函数式编程
在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 = 20def fn3():# a = 10 # 在函数中为变量赋值时,默认都是为局部变量赋值# 如果希望在函数内部修改全局变量,则需要使用global关键字,来声明变量global a # 声明在函数内部的使用a是全局变量,此时再去修改a时,就是在修改全局的aa = 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 = nfor i in range(1,n):result *= ireturn result
递归函数
# 创建一个函数,用来检查一个任意的字符串是否是回文字符串,如果是返回True,否则返回False
def hui_wen(s):'''该函数用来检查指定的字符串是否回文字符串,如果是返回True,否则返回False参数:s:就是要检查的字符串'''# 基线条件if len(s) < 2 :# 字符串的长度小于2,则字符串一定是回文return Trueelif 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 Truereturn False # 这个函数用来检查指定的数字是否大于5
def fn3(i):if i > 5 :return True return Falsedef 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 averageraverager = 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 == 0r = 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 reducel = [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 + breturn rdef mul(a , b):'''求任意两个数的积'''r = a * breturn 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 rr = 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_functionf = 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()函数实现了求多个数的平均值的功能。
装饰器是一种用于修改已有函数行为的函数。它可以在不修改原函数代码的情况下,给函数添加新的功能。在实操练习中,使用装饰器实现了对函数进行扩展的功能。
总结:函数式编程通过将函数视为一等对象,使得程序更具灵活性和可扩展性。它包括了一等对象、作用域、命名空间等概念,并可以通过高阶函数、递归函数、装饰器等实现各种功能。函数式编程是一种强大的编程范式,能够简化复杂问题的解决过程。