函数式编程
在命令式的编程范式当中,你通过告诉计算机一系列需要执行的任务并在计算机执行以后以完成你的目的。当执行任务的时候,状态可能会发生改变。比如,假设你原先将A赋值为5,然后你改变A的数值。你会有很多变量,并且变量内部的值也会发生改变。
正因为如此,变量不能变化。当时设置好了一个变量,它将永远保持那样的方式(注意下,在纯粹的函数式编程语言当中通常不称之为变量)。因此,在函数式编程范式当中,函数不会有副作用。函数的副作用是指函数修改了一些函数作用范围外的东西。在函数式编程范式当中,改变变量的值是不容许的事情,同样影响在函数作用域之外的变量也是不被容许的。函数唯一可以做的事情是做一些计算然后以结果的形式返回。
如果一个函数用同样的参数调用两次,应该确保返回同样的结果。如果你学习了数学函数,你会了解这种机制的好处。这个叫做参考透明(referengtial transparency),因为函数没有副作用,如果你构建一个用来计算的程序,你可以提供程序的运算速度。如果程序知道func(2)等于3,我们可以将这个结果保存到一个表格当中。当我们已经知道函数运行结果的时候,这样可以防止重新运行同样的函数。
典型的,在函数式编程当中,我们不使用循环。我们采用递归。递归是一个数学上的概念,通常,它意味着“自己调用自己”。在一个递归的函数当中,函数重复地以子函数的形式调用自己。
Map
要理解map,让我们首先看看什么是可迭代的。可迭代的就是任何你可以迭代的东西(翻译起来好拗口……)。典型的有列表(list)和数组(array),不过python有很多不同可迭代的类型。你甚至可以通过实现一些神奇的方法创建自己的可迭代的对象。一个神奇的方法像一个可以让你的对象更加Pythonic的API。你需要实现两个神奇方法来使得一个对象是可迭代的:
class Counter:
def __init__(self, low, high):
# set class attributes inside the magic method __init__
# for "inistalise"
self.current = low
self.high = high
def __iter__(self):
# first magic method to make this object iterable
return self
def __next__(self):
# second magic method
if self.current > self.high:
raise StopIteration
else:
self.current += 1
return self.current - 1
第一个神奇方法,“_iter_”或者dunder iter(双下划线开头的iter)返回可迭代的对象,这个经常在循环的开头使用。Dunder下一个返回下一个对象。
在python里面,一个迭代器是指只有__iter__方法的对象。这意味着你可以访问这个对象的位置,但是不能迭代遍历这个对象。一些对象会有__next__方法而没有__iter__方法,比如sets。我们假设我们所接触到的每个对象都是一个可迭代的对象。
到目前为止,我们知道一个可迭代的对象是怎么样的,让我们回到map函数。map函数允许我们将一个函数作用于一个可迭代对象当中没一个元素。通常我们要将一个函数作用于列表中的每一个元素,并且我们知道对于大部分可迭代对象都是可能的。Map函数有两个输入,一个是需要作用的函数,另外一个是可迭代的对象。
Reduce
Reduce函数将一个可迭代的对象转变成一个元素。通常你将一个计算作用于一个列表然后将它reduce到一个数字。Reduce看起来是这个样子的:
reduce(function, list)
我们可以(并且经常如此)使用lambda表达式作为函数。
一个列表的阶乘是将任意一个单一的数字相乘。为了达到这样的目的,你将编码如下:
product = 1
x = [1, 2, 3, 4]
for num in x:
product = product * num
但是通过reduce我们可以这样写:
from functools import reduce
product = reduce((lambda x, y: x * y), [1, 2, 3, 4])
得到同样的阶乘结果。代码更加简洁,加入函数式编程的知识代码更为有序。
Filter
filter函数传入一个迭代对象作为参数并将这个迭代对象当中所有那些你不要的东西滤去。通常,filter传入一个函数和一个列表。将这个函数作用于列表当中的任意一个元素加入函数返回True,不做任何事情。加入返回False,将这个元素从列表当中删除。语法看起来这样:
filter(function, list)
让我们看一个简单的例子,不使用filter我们这样编码:
x = range(-5, 5)
new_list = []
for num in x:
if num < 0:
new_list.append(num)
通过filter,编码是这样的:
x = range(-5, 5)
all_less_than_zero = list(filter(lambda num: num < 0, x))
partial
部分应用(又叫做闭包)更为复杂,但是超级酷。你可以调用一个函数,但不提供它所需要的全部参数。让我们通过一个例子看看这个过程。我们要创建一个函数需要传入两个参数,一个base和一个exponet,然后返回base的exponent次方。如下所示:
def power(base, exponent):
return base ** exponent
这个可以工作,但是如果需要3次方的函数呢?或者一个需要进行4次方运算的函数呢?我们能够一直那样子写吗?当然,你是可以的。但是程序员都是懒惰的。如果你一遍又一遍地重复一件事情,这就意味着有一个更快的方式加速速度而不用去做重复的事情。我们可以采用部分应用的方式。让我看一个采用部分应用的square函数的例子:
from functools import partial
square = partial(power, exponent = 2)
print(square(2))
List comprehensions
早些时候,我提到任何map或者filter能够做的事情,你都可以用list comprehension来实现。这部分内容我们将好好学习一下list comprehension。
一个list comprehension是Python产生列表的一种方式。List comprehensions支持这样的if表达式。你不再需要将上百万个函数作用于一些东西然后得到你所想要的。事实上,假如你试着做一些改变让列表看起来更加清晰和简单,那么使用list comprehension是一个不错的选择。