30天学习Python 第十一天——函数式编程

介绍

面向对象和函数式编程都基于同一个原则——关注点分离。面向对象编程将属性和操作属性的方法都封装在一个class中,而函数式编程将属性和操作属性的方法分离,并且使用函数对对象执行操作。

今天,我将探讨函数式编程在Python中的应用,并谈论如何实现函数式编程来组织python代码。

在开发者中,哪一种编程范式更好经常会有争议。许多人执着于编写纯面向对象代码或纯函数代码。更实用的方式是理解这两种范式的优点和缺点,必要时接受二者的优点。

下面是函数式编程中的重要概念,以及它们在Python中的实现。

纯函数

概念

纯函数是函数式编程的核心,就像类和对象在面对对象编程中的地位一样。

纯函数是一个函数,但它遵循两条规则:

  • 相同的输入,总是得到相同的输出
  • 没有副作用

副作用

副作用的意思是改变函数作用域之外的数据,比如打印到控制台、网络请求、修改数据库、接受一个全局变量等。

def doubler(num):
  '''
  接受一个数字并让它乘2
  '''
  return num * 2print(douber(5))复制代码
def emoji_appender(list, emoji):
  '''
  接收一个列表和一个表情,并将表情添加到列表中的每一项
  '''
  new_list = []  for item in list:
    new_list.append(str(item) + emoji)  return new_list# 改变了函数作用域之外的列表,所以不纯print(emoji_appender([1,2,3], '????')) # ['1????', '2????', '3????']print(emoji_appender(['alpha','beta','gamma'], '????')) 
# ['alpha????', 'beta????', 'gamma????']复制代码

纯函数有什么好处

一个纯函数在理想情况下应该只执行一个特定的操作。因为纯函数总是接收相同的输入,返回相同的输出。纯函数很容易测试,因为它们是可预测的。这种可预测性可以使多个纯函数并行,因为它们没有副作用(不会影响到彼此)。这会使代码容易阅读和理解。

几个重要的内置函数

Python中有一些内置的函数可以使代码以函数式的风格编写或者以更加声明式的方式。这些函数是纯函数,不会修改输入数据,也不会产生任何副作用。

.map()

map函数接受一个函数作为执行某种操作的第一个参数,并接受一个可迭代对象作为第二个参数。它循环遍历可迭代对象的每一项,并且对其应用传递的函数。

numbers = [1,2,3,4,5]def multiply_by5(num):
  return num * 5result = map(multiply_by5, numbers)

print(result) # <map object at 0x7f572dcb7730> (map对象的内存地址)print(list(result)) # [5, 10, 15, 20, 25] (更改后的列表)print(numbers) # [1,2,3,4,5] (原列表未被更改)`复制代码

map函数返回一个map对象内存地址的引用。想要得到一个复合的数据(译者注:被多个函数处理),则需要传递一个函数列表作为第一个参数。需要特别注意的一点是:map函数的输入和输出有相同的产股的。map函数不会改变输入的可迭代对象。

.filter()

顾名思义,filter函数根据传递的函数过滤输入的可迭代数据。

color_to_remove = 'red'colors = ['blue', 'green', 'black', 'red']def remove_color(color):
  return color != color_to_remove

result = filter(remove_color, colors)

print(list(result)) # ['blue', 'green', 'black']print(colors) # ['blue', 'green', 'black', 'red'] (没有改变原列表)复制代码

像map一样,filter函数返回被过滤对象的内存地址引用,为了获得实际结果,需要将其传递给list函数(译者注:上面代码中的list函数)。返回值对象的长度等于或小于输入的可迭代对象的长度,这取决于输入的条件函数。filter同样不会改变输入的数据。

zip()

zip是一个内置函数,它接受多个可迭代对象,并且把它们组合或者“压缩”成一个元组。在一个数据库中,不同的用户数据存储在不同的列中,需要根据它们之间的关系将它们组合在一起时,这个特性很有用。

emails = ['alan@gmail.com', 'ross@gmail.com']
usernames = ['alan', 'ross']

users = list(zip(emails,usernames))
print(users) # [('alan@gmail.com', 'alan'), ('ross@gmail.com', 'ross')]print(emails) # ['alan@gmail.com', 'ross@gmail.com'] (Unmodified)print(usernames) # ['alan', 'ross']复制代码

reduce()

reduce和上面的其他函数不太一样。reduce不是python的内置函数。它是一个包或工具包的一部分,和python解释器和包一起下载。所以它需要从functookl模块引入。后面我们会探讨更多关于模块的内容。

reduce有一丢丢的不好理解。但是,从JavaScript中借鉴过来,array的reduce方法能做同样的事情。它在累加器中保持对合成值的追踪。

reduce接受一个函数和一个可迭代对象作为必要参数,还有一个可选的初始化值参数,这个参数的默认值为0。

可以将reduce理解为是一种能够通过将一个可迭代对象较少或合并为单个值的方法。

from functools import reduce

numbers = [1,2,3,4]def accumulator(acc, curr):
  return acc + currsum = reduce(accumulator, numbers, 0)
print(sum) # 10复制代码

这就是今天的全部内容了。核心的概念大部分已经探讨过,我相信函数式编程的概念在后面高级的主题中我们还会遇到的。我将在明天详细介绍其余几个函数式编程术语,并以一种清晰的方式与大家分享。