Python教程 - 10基础语法函数式编程

更好的阅读体验:点这里www.foooor.com

10 函数式编程

什么是函数式编程?

就是用函数来解决问题,函数可以赋值给变量、函数可以作为参数传递给另外一个函数、函数可以作为返回值。

10.1 函数作为参数

1 函数赋值给变量

函数可以赋值给变量,看一下下面的代码:

def compute(x, y):			# 定义一个函数
    return x + y

func = compute					# 将函数赋值给变量,注意函数名称后面没有括号
result = func(1, 2)			# 通过变量调用函数
print(result)

将函数赋值给变量,注意函数后面没有括号,有括号就是调用函数了。

函数赋值给变量后,可以通过变量调用函数。

2 函数作为参数

函数可以作为参数传递给另外一个函数

def plus(x, y):											# 定义一个加法的函数
    return x + y

def multiply(x, y):									# 定义一个乘法的函数
    return x * y

def test_func(x, y, func):					# 第三个参数是一个函数,在该函数中调用了这个函数
    return func(x, y)


result1 = test_func(1, 2, plus)			# 传递三个参数,第三个参数是一个函数
print(result1)

result2 = test_func(1, 2, multiply)
print(result2)

在上面的代码中,我们将两个数字和一个函数作为参数传递给了 test_func 函数,在 test_func 函数中,将两个数字作为参数,调用了作为参数的函数。

这样根据传入的函数的不同,实现了不同的逻辑,这是计算逻辑的传递,而不是数据的传递。

这样做有什么好处呢?

3 函数作为参数的应用

我们现在有一个学生列表:

class Student:
    def __init__(self, name, chinese, math, english):
        self.name = name
        self.chinese = chinese
        self.math = math
        self.english = english
        
    def __str__(self):
        return f"name:{self.name}, chinese:{self.chinese}, math:{self.math}, english:{self.english}"

# 学生列表
all_stu_list = [Student("zhangsan", 87, 48, 92),
            		Student("lisi", 47, 92, 71),
            		Student("wangwu", 58, 46, 38),
            		Student("zhangliu", 95, 91, 99)]

现在想获取语文成绩不及格的同学、所有课程不及格的同学、所有课程都是优秀的学生,那么我们需要定义三个函数,如下:

# 获取语文成绩不及格的同学
def get_chinese_flunk_list():
    stu_list = []
    for stu in all_stu_list:
        if stu.chinese < 60:
            stu_list.append(stu)
    return stu_list

# 获取所有课程不及格的同学
def get_flunk_list():
    stu_list = []
    for stu in all_stu_list:
        if stu.chinese < 60 and stu.math < 60 and stu.english < 60:
            stu_list.append(stu)
    return stu_list

# 获取所有课程都是优秀的学生
def get_excellent_list():
    stu_list = []
    for stu in all_stu_list:
        if stu.chinese >= 90 and stu.math >= 90 and stu.english >= 90:
            stu_list.append(stu)
    return stu_list
  
  
# -------------方法调用---------------------
print("语文不及格的同学:")
stu_list = get_chinese_flunk_list()
for stu in stu_list:
    print(stu)

print("所有不及格的同学:")
stu_list = get_flunk_list()
for stu in stu_list:
    print(stu)

print("所有成绩都是优秀的同学:")
stu_list = get_excellent_list()
for stu in stu_list:
    print(stu)

功能是实现了,但是上面的代码有一个问题,就是代码复用性差。上面定义了3个函数 get_chinese_flunk_list get_flunk_list, get_excellent_list,只有其中的判断条件不同。 能否抽出判断条件,将判断条件作为参数传递呢?

我们先抽出一个公共的,根据条件获取学生列表的函数:

# 根据条件获取学生列表
def get_stu_list_by_condition(fun_condition):
    stu_list = []
    for stu in all_stu_list:
        if fun_condition(stu):
            stu_list.append(stu)
    return stu_list

上面的参数传进来的是一个函数,在 if 的判断后面执行这个函数,如果这个函数返回的是true,则将元素添加到列表中。

然后我们再编写三个获取筛选学生条件的函数,根据传进来的参数进行判断:

# 获取语文成绩不及格的同学
def get_chinese_flunk_condition(stu):
    return stu.chinese < 60

# 获取所有课程不及格的同学
def get_flunk_condition(stu):
    return stu.chinese < 60 and stu.math < 60 and stu.english < 60

# 获取所有课程都是优秀的学生
def get_excellent_condition(stu):
    return stu.chinese >= 90 and stu.math >= 90 and stu.english >= 90

然后我们可以根据条件获取相应的学生了,都是调用共用的函数 get_stu_list_by_condition, 传入不同的条件获取不同的学生列表:

print("语文不及格的同学:")
stu_list = get_stu_list_by_condition(get_chinese_flunk_condition)
for stu in stu_list:
    print(stu)

print("所有不及格的同学:")
stu_list = get_stu_list_by_condition(get_flunk_condition)
for stu in stu_list:
    print(stu)

print("所有成绩都是优秀的同学:")
stu_list = get_stu_list_by_condition(get_excellent_condition)
for stu in stu_list:
    print(stu)

这样已函数作为参数进行逻辑的控制,代码更为优雅,复用性更高。

完整代码:

class Student:
    def __init__(self, name, chinese, math, english):
        self.name = name
        self.chinese = chinese
        self.math = math
        self.english = english

    def __str__(self):
        return f"name:{self.name}, chinese:{self.chinese}, math:{self.math}, english:{self.english}"


all_stu_list = [Student("zhangsan", 87, 48, 92),
            Student("lisi", 47, 92, 71),
            Student("wangwu", 58, 46, 38),
            Student("zhangliu", 95, 91, 99)]


# 根据条件获取学生列表
def get_stu_list_by_condition(fun_condition):
    stu_list = []
    for stu in all_stu_list:
        if fun_condition(stu):
            stu_list.append(stu)
    return stu_list


# 获取语文成绩不及格的同学
def get_chinese_flunk_condition(stu):
    return stu.chinese < 60

# 获取所有课程不及格的同学
def get_flunk_condition(stu):
    return stu.chinese < 60 and stu.math < 60 and stu.english < 60

# 获取所有课程都是优秀的学生
def get_excellent_condition(stu):
    return stu.chinese >= 90 and stu.math >= 90 and stu.english >= 90


print("语文不及格的同学:")
stu_list = get_stu_list_by_condition(get_chinese_flunk_condition)
for item in stu_list:
    print(item)

print("所有不及格的同学:")
stu_list = get_stu_list_by_condition(get_flunk_condition)
for item in stu_list:
    print(item)

print("所有成绩都是优秀的同学:")
stu_list = get_stu_list_by_condition(get_excellent_condition)
for item in stu_list:
    print(item)

执行结果:

语文不及格的同学:
name:lisi, chinese:47, math:92, english:71
name:wangwu, chinese:58, math:46, english:38
所有不及格的同学:
name:wangwu, chinese:58, math:46, english:38
所有成绩都是优秀的同学:
name:zhangliu, chinese:95, math:91, english:99

4 lambda表达式

上面作为参数传递的函数,使用lambda表达式来写,可以更简洁。

lambda表达式的语法:

lambda 参数: 方法体

方法体不需要return语句,会自动返回。

以获取语文成绩不及格的条件函数为例,我们可以将函数 get_chinese_flunk_condition 改写成 lambda表达式。

# 获取语文成绩不及格的同学
def get_chinese_flunk_condition(stu):
    return stu.chinese < 60

改写为:

lambda stu: stu.chinese < 60

将另外两个函数也改成 lambda 表达式,在调用 get_stu_list_by_condition 函数的时候,就不用传入函数了,省去了3个获取条件的函数:

print("语文不及格的同学:")
stu_list = get_stu_list_by_condition(lambda stu: stu.chinese < 60)
for item in stu_list:
    print(item)

print("所有不及格的同学:")
stu_list = get_stu_list_by_condition(lambda stu: stu.chinese < 60 and stu.math < 60 and stu.english < 60)
for item in stu_list:
    print(item)

print("所有成绩都是优秀的同学:")
stu_list = get_stu_list_by_condition(lambda stu: stu.chinese >= 90 and stu.math >= 90 and stu.english >= 90)
for item in stu_list:
    print(item)

代码是不是更简洁了。

lambda就是为了将方法作为参数而存在的,当然如果你将lambda表达式赋值给一个变量,也不是不可以,只是有点没事找抽,那还不如定义成原来函数的模样。

get_chinese_flunk_condition = lambda stu: stu.chinese < 60

print("语文不及格的同学:")
stu_list = get_stu_list_by_condition(get_chinese_flunk_condition)
for item in stu_list:
    print(item)

需要注意:lambda表达式的方法体只能有一句代码,无法写多行。

lambda表达式相对于传统的函数,它是随时创建随时销毁的,不会保存在方法区中。

完整代码:

class Student:
    def __init__(self, name, chinese, math, english):
        self.name = name
        self.chinese = chinese
        self.math = math
        self.english = english

    def __str__(self):
        return f"name:{self.name}, chinese:{self.chinese}, math:{self.math}, english:{self.english}"


all_stu_list = [Student("zhangsan", 87, 48, 92),
            Student("lisi", 47, 92, 71),
            Student("wangwu", 58, 46, 38),
            Student("zhangliu", 95, 91, 99)]


# 根据条件获取学生列表
def get_stu_list_by_condition(fun_condition):
    stu_list = []
    for stu in all_stu_list:
        if fun_condition(stu):
            stu_list.append(stu)
    return stu_list


print("语文不及格的同学:")
stu_list = get_stu_list_by_condition(lambda stu: stu.chinese < 60)
for item in stu_list:
    print(item)

print("所有不及格的同学:")
stu_list = get_stu_list_by_condition(lambda stu: stu.chinese < 60 and stu.math < 60 and stu.english < 60)
for item in stu_list:
    print(item)

print("所有成绩都是优秀的同学:")
stu_list = get_stu_list_by_condition(lambda stu: stu.chinese >= 90 and stu.math >= 90 and stu.english >= 90)
for item in stu_list:
    print(item)

5 lambda表达式的常见写法

无参函数改写为lambda

def func():
    return 100

# 改写为:
lambda : 100

多参数函数改写为lambda

def func(x, y):
    return x + y

# 改写为:
lambda x, y: x + y

没有返回值的函数

lambda表示也是可以没有返回值的。

def func(x):
    print(x)

# 改写为:
lambda x: print(x)

6 内置高阶函数

内置高阶函数也就是Python内置的可接受函数的方法。

map函数

map函数接收两个参数:一个函数和一个可迭代对象。map函数将传递给它的函数应用于可迭代对象的每个元素,返回一个新的可迭代对象,其中每个元素都是执行传递的函数后的结果。

举个栗子:

# 将列表中的每个元素乘以2
my_list = [1, 2, 3, 4, 5]
result = map(lambda x: x*2, my_list)
print(list(result)) 	 				# [2, 4, 6, 8, 10]

在这个例子中,我们将一个lambda函数应用于my_list中的每个元素,将每个元素乘以2。我们使用list()函数将结果打印出来。

需要注意,map返回的结果不是一个list,而是一个类似于迭代器的可迭代对象,可以使用 list() 函数转换为列表

filter函数

filter函数接收两个参数:一个函数和一个可迭代对象。filter函数将传递给它的函数应用于可迭代对象的每个元素,并返回一个新的可迭代对象,其中包含使传递的函数返回True的元素。

举个栗子:

# 过滤掉列表中的奇数
my_list = [1, 2, 3, 4, 5]
result = filter(lambda x: x % 2 == 0, my_list)
print(list(result))  # [2, 4]

在这个例子中,我们使用lambda函数筛选出my_list中的偶数。我们使用list()函数将结果打印出来。

sorted函数

sorted函数接收一个可迭代对象和两个可选参数:key和reverse。sorted函数将传递给它的可迭代对象进行排序,并返回一个新的已排序的列表。如果指定了key参数,则将其用作排序关键字。如果指定了reverse参数,则将其用作布尔值,以指定是否应按相反的顺序对列表进行排序。

举个栗子:

# 对列表进行排序
my_list = [3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5]
result = sorted(my_list)
print(result)  # [1, 1, 2, 3, 3, 4, 5, 5, 5, 6, 9]

# 按字符串长度对列表进行排序
my_list = ["apple", "banana", "pear", "orange"]
result = sorted(my_list, key=len)
print(result)  # ['pear', 'apple', 'banana', 'orange']

在第一个例子中,我们对my_list进行排序,以便我们可以使用sorted函数返回已排序的列表。在第二个例子中,我们使用key参数按字符串长度对my_list进行排序。我们使用print()函数将结果打印出来。

max函数

max函数接收一个可迭代对象,并返回其中的最大值。

举个栗子:

# 获取列表中的最大值
my_list = [3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5]
result = max(my_list)
print(result)  # 9

在这个例子中,我们使用max函数返回my_list中的最大值。我们使用print()函数将结果打印出来。

min函数

min函数收一个可迭代对象,并返回其中的最小值。

举个栗子:

# 获取列表中的最小值
my_list = [3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5]
result = min(my_list)
print(result)  # 1

在这个例子中,我们使用min函数返回my_list中的最小值。我们使用print()函数将结果打印出来。

10.2 外部嵌套作用域

以前已经讲过了关于作用域的一些问题,使用 global 关键字,在函数内部修改全局的变量。

下面来讲解一下外部嵌套作用域。

1 外部嵌套作用域

什么是外部嵌套作用域?

外部嵌套作用域就是允许在一个函数内部定义另一个函数,并让内部函数访问外部函数的变量。

举个栗子:

我们在一个函数内部定义了另外一个函数,并调用了这个内部函数。

def outer_func():
    a = 1

    def inner_func():
        print(a)

    inner_func()        # 调用内部函数
    
outer_func()            # 调用外部函数

执行结果:

1

现在我们尝试在内部函数中修改外部函数中定义的变量:

def outer_func():
    a = 1

    def inner_func():
        a = 2						# 并不能修改外部函数的变量,这里是在内部函数中又创建了和外部函数同名的变量
        print(a)

    inner_func()        # 调用内部函数
    print(a)

outer_func()            # 调用外部函数

执行结果:

2

1

可以看到在内部函数中并没能修改外部函数中的变量。

2 nonlocal

我们可以使用 nonlocal 关键字,在内部函数中修改外部函数中的变量。

举个栗子:

def outer_func():
    a = 1

    def inner_func():
        nonlocal a      # 声明外部嵌套作用域
        a = 2
        print(a)

    inner_func()        # 调用内部函数
    print(a)

outer_func()            # 调用外部函数

执行结果:

2

2

总结:

  • 外部嵌套作用域允许内部函数访问外部函数的变量。
  • 内部函数可以访问外部函数的变量,但是不能对其进行修改。
  • 如果需要修改外部函数中的变量,可以使用 Python 中的 nonlocal 关键字。

10.3 函数作为返回值

1 闭包

什么是闭包?

闭包必须满足三个条件:

  • 在一个外部函数中有一个内部函数
  • 内部函数必须引用外部函数中的变量
  • 外部函数的返回值必须是内部函数

语法:

def 外部函数名(参数):
		外部变量
  
  	def 内部函数名(参数):
        使用外部函数变量
    
    return 内部函数名

举个栗子:

def outer_func():
    a = 1

    def inner_func():
        print(a)						# 使用了外部函数的变量

    return inner_func;			# 将内部函数返回,这里函数名后没有括号


result = outer_func()				# 这里得到的结果是一个函数
result()										# 执行内部函数,结果为:1

闭包代码运行的内存图

首先代码自上而下执行,遇到函数 outer_func(),会将函数加载到方法区,并不会执行函数。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

然后执行代码:result = outer_func(), 创建 outer_func 函数的栈帧,执行函数 outer_func(),执行 a = 1,然后定义了函数 inner_func(),则将函数 inner_func() 存储到方法区,此时并没有执行函数 inner_func(),然后将函数inner_func() 返回,相当于创建了一个变量 inner_func 指向了方法取中的函数 inner_func()

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

执行完函数 inner_func ,将结果赋值给了变量result,然后执行result,相当于执行了函数 outer_func,则开辟了函数 outer_func 的栈帧。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

由于闭包是将内部函数返回了,所以外部函数执行完毕后,栈帧并不会立即销毁,而是等待内部函数执行。

上图蓝色的区域的环境也就是闭包的环境。

下面来讲解一下闭包的实际应用。

2 装饰器

什么是装饰器?

装饰器其实就是闭包的一种应用, 其功能就是在不破坏目标函数原有的代码和功能的前提下,为目标函数增加新功能。

例如,我现在有一个洗澡的函数:

def bath():
    print("我在洗澡...")

bath()

但是我在洗澡之前需要脱衣服,在洗澡之后需要穿衣服,于是我修改代码:

def bath():
    print("脱衣服")
    print("我在洗澡...")
    print("穿衣服")

bath()

这样就需要修改洗澡的方法,我能否不修改洗澡的方法,在洗澡前后做一些事情呢?

可以使用装饰器写法:

def bath_wrapper(func):		# 使用闭包,在函数内部定义一个函数,对传递进来的函数进行包装
    def wrapper():
        print("脱衣服")
        func()
        print("穿衣服")

    return wrapper;

def bath():								# 洗澡方法不变
    print("我在洗澡...")


func = bath_wrapper(bath)	# 返回的是一个经过包装的函数
func()										# 执行包装的函数

洗澡的函数不变,我们重新定义了一个函数,在函数内部定义了内部函数,对要执行的函数 func() 进行了包装,在函数前后做了一些事情。

上面的写法仍然有缺点,就是我调用的时候,需要调用包装的函数,得到一个新函数再执行,还可以再进行优化:

def bath_wrapper(func):		# 使用闭包,在函数内部定义一个函数,对传递进来的函数进行包装
    def wrapper():
        print("脱衣服")
        func()
        print("穿衣服")

    return wrapper;

@bath_wrapper
def bath():								# 洗澡方法不变
    print("我在洗澡...")

   
# 直接执行洗澡的函数,会自动执行包装函数
bath()

我们通过注解的方式,将包装的函数添加到了 bath 函数上,这样可以直接调用原来的函数,便会执行包装函数中封装的内容。

执行结果:

脱衣服
我在洗澡…
穿衣服

但是现在仍然有一个问题,就是传参问题,尤其是当我还有一个睡觉的方法,我在睡觉前后也要脱衣服和穿衣服,而且洗澡和睡觉的参数不一样。下面的代码就会报错,因为包装函数中没有处理参数:

def bath_wrapper(func):		# 使用闭包,在函数内部定义一个函数,对传递进来的函数进行包装
    def wrapper():
        print("脱衣服")
        func()
        print("穿衣服")

    return wrapper;


@bath_wrapper
def bath(singer, song):
    print(f"我在洗澡, 唱一首{singer}{song}")

@bath_wrapper
def sleep(duration):
    print(f"我在睡觉, 睡{duration}小时")

# 直接执行洗澡的函数,会自动执行包装函数
bath("偶像", "鸡你太美")
sleep(8)

需要修改代码如下:

def bath_wrapper(func):		# 使用闭包,在函数内部定义一个函数,对传递进来的函数进行包装
    def wrapper(*args, **kwargs):		# 接受各种传参方式
        print("脱衣服")
        func(*args, **kwargs)				# 将参数拆开传递
        print("穿衣服")

    return wrapper;


@bath_wrapper
def bath(singer, song):
    print(f"我在洗澡, 唱一首{singer}{song}")

@bath_wrapper
def sleep(duration):
    print(f"我在睡觉, 睡{duration}小时")

# 直接执行洗澡的函数,会自动执行包装函数
bath("偶像", "鸡你太美")
sleep(8)

函数的传参问题,可以见 函数 --> 函数的参数 章节。

执行结果:

脱衣服
我在洗澡, 唱一首偶像的鸡你太美
穿衣服
脱衣服
我在睡觉, 睡8小时
穿衣服

3 在函数之间共享数据

使用闭包可以在内部函数和外部函数之间共享数据。

举个栗子:

def counter():
    count = 0

    def inner():
        nonlocal count
        count += 1
        return count
    return inner


c1 = counter()          # 创建了一个计数器
print(c1())  # 输出 1
print(c1())  # 输出 2

c2 = counter()          # 创建了第二个计数器
print(c2())  # 输出 1

在上面的代码中,创建了闭包,用来实现了一个计数器的功能。

当我们调用外部函数 counter 就会创建一个计数器,当我们调用这个计数器的时候,count 的值就会加1,由于我们每次调用counter函数时都会创建一个新的闭包,因此我们可以创建多个计数器,每个计数器都是独立的。

执行结果:

1

2

1

其实,我们也可以创建一个计数器的类,在类中定义一个count属性,也可以实现上面的功能,例如:

class Counter:
    def __init__(self):
        self.count = 0
    
    def increment(self):
        self.count += 1
        return self.count
   
  
# 创建计数器
counter1 = Counter()
counter2 = Counter()
print(counter1.increment())  # 输出 1
print(counter2.increment())  # 输出 1

一般情况下,类实现方式更适合需要维护的数据结构较为复杂的情况,比如需要使用多个属性和方法来处理数据。而闭包实现方式更适合只需要共享一些简单数据的情况。

4 创建私有变量

我们之前讲过类的私有变量,但其实那只是骗人的障眼法,也是可以调用和修改的。但是我们使用闭包可以实现私有变量。

举个栗子:

下面的代码并没有使用类,而是使用了函数实现了类的功能。

在下面的代码中,我们定义了一个函数 create_person,可以通过它来返回对象,只是这个对象是字典的形式。

在类中定义age变量。

def create_person(name):
    age = 0

    def get_age():
        nonlocal age
        return age

    def set_age(new_age):
        nonlocal age
        if new_age >= 0:
            age = new_age
        else:
            print("Invalid age")

    def get_name():
        return name

    return {
        'get_name': get_name,
        'get_age': get_age,
        'set_age': set_age
    }


person = create_person("逗比")
print(person['get_name']())  # 输出 Alice
print(person['get_age']())   # 输出 0
person['set_age'](30)
print(person['get_age']())   # 输出 30

通过上面的代码可以看出,我们是无法直接访问和修改 age 变量的,必须通过 get_ageset_age 内部函数才可以,因此它对外部代码是不可见的,可以被看作是一个私有变量。

name 只能通过 get_name 函数来获取,也是无法修改的。

同时和类很相似,我们调用一次 create_person 函数,就会创建一个对象。

5 作为回调函数

回调函数指的是将一个函数作为参数传递给另外一个函数,然后在这个函数中执行这个参数函数。回调函数通常用于异步编程中,可以在事件发生后回调执行,以完成一些特定的任务。例如:在实际的功能实现中,我们经常会做一些耗时的操作,例如网络请求,发起网络请求后,继续执行后面的代码,待服务器返回结果后,在通过回调函数的方式返回结果。

这里举一个回调函数的列子:

def plus(a, b, callback):					# callback参数就是一个函数
    result = a + b
    callback(result)							# 执行callback函数将结果返回


def print_result(result):
    print("The result is:", result)


plus(10, 20, print_result)  # 输出 The result is: 30

调用 plus 函数的时候,传入 print_result 函数,得到计算结果后,调用 print_result 函数,并传递结果。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

山石岐渡

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值