less函数_教程|⼗分钟学会函数式Python!
函数式编程到底是什么?本⽂将详解其概念,同时分享怎样在 Python 中使⽤函数式编程。主要内容包括列表解析式和其他形式的解析式。函数式模型
在命令式模型中,执⾏程序的⽅式是给计算机⼀系列指令让它执⾏。执⾏过程中计算机会改变状态。例如,⽐如 A 的初始值是 5,后来改
变了 A 的值。那么 A 就是个变量,⽽变量的意思就是包含的值会改变。
⽽在函数式模式中,你不需要告诉计算机做什么,⽽是告诉计算机是什么。⽐如数字的最⼤公约数是什么,1 到 n 的乘积是什么等等。
因此,变量是不能被改变的。变量⼀旦被设置,就永远保持同⼀个值(注意在纯粹的函数式语⾔中,它们不叫变量)。因此,在函数式模型中,函数没有副作⽤。副作⽤就是函数对函数外的世界做出的改变。来看看下⾯这段Python代码的例⼦:
a = 3def some_func(): global a a = 5some_func()print(a)
代码的输出是 5。在函数式模型中,改变变量的值是完全不允许的,让函数影响函数外的世界也是不允许的。函数唯⼀能做的就是做⼀些计算然后返回⼀个值。
你可能会想:“没有变量也没有副作⽤?这有什么好的?”好问题。
如果函数使⽤同样的参数调⽤两次,那么我们可以保证它会返回同样的结果。如果你学过数学函数,
你肯定知道这样做的好。这叫做引⽤透明性(referential transparency)。由于函数没有副作⽤,那么我们可以加速计算某个东西的程序。⽐如,如果程序知道 func(2)返回 3,
那么可以将这个值保存在表中,这样就不需要重复运⾏我们早已知道结果的函数了。
通常,函数式编程不使⽤循环,⽽是使⽤递归。递归是个数学概念,通常的意思是“把结果作为⾃⼰的输⼊”。使⽤递归函数,函数可以反复调⽤⾃⼰。下⾯就是个使⽤Python定义的递归函数的例⼦:
def factorial_recursive(n): # Base case: 1! = 1 if n == 1: return 1 # Recursive case: n! = n * (n-1)! else: return n * factorial_recursive(n-1)
函数式编程语⾔也是懒惰的。懒惰的意思是,除⾮到最后⼀刻,否则它们不会执⾏计算或做任何操作。如果代码要求计算2+2,那么函数式程序只有在真正⽤到计算结果的时候才会去计算。我们马上就会介绍Python中的这种懒惰。
映射
要理解映射(map),⾸先需要理解什么是可迭代对象。可迭代对象(iterable)指任何可以迭代的东西。通常是列表或数组,但 Python 还有
许多其他可迭代对象。甚⾄可以⾃定义对象,通过实现特定的魔术⽅法使其变成可迭代对象。魔术⽅法就像 API ⼀样,能让对象更有Python 风格。要让对象变成可迭代对象,需要实现以下两个魔术⽅法:
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__”(双下划线iter)返回迭代⼦,通常在循环开始时调⽤。__next__则返回迭代的下⼀个对象。
可以打开命令⾏试⼀下下⾯的代码:
for c in Counter(3, 8): print(c)
这段代码将会输出:
345678
在 Python 中,迭代器就是只实现了__iter__魔术⽅法的对象。也就是说,你可以访问对象中都包含的位置,但⽆法遍历整个对象。⼀些对象实现了__next__魔术⽅法,但没有实现__iter__魔术⽅法,⽐如集合(本⽂稍后会讨论)。在本⽂中,我们假设涉及到的⼀切对象都是可迭代的对象。
现在我们知道了什么是可迭代的对象,回过头来讨论下映射函数。映射可以对可迭代对象中的每个元素执⾏指定的函数。通常,我们对列表中的每个元素执⾏函数,但要知道映射其实可以针对绝⼤多数可迭代对象使⽤。
map(function, iterable)
假设有⼀个列表由以下数字组成:
[1, 2, 3, 4, 5]
我们希望得到每个数字的平⽅,那么代码可以写成这样:
x = [1, 2, 3, 4, 5]def square(num): return num*numprint(list(map(square, x)))
Python中的函数式函数是懒惰的。如果我们不加“list()”,那么函数只会将可迭代对象保存下来,⽽不会保存结果的列表。我们需要明确地告诉Python“把它转换成列表”才能得到结果。
在Python中⼀下⼦从不懒惰的函数求值转换到懒惰的函数似乎有点不适应。但如果你能⽤函数式的思维⽽不是过程式的思维,那么最终会适应的。
这个“square(num)”的确不错,但总觉得有点不对劲。难道为了仅使⽤⼀次的map就得定义整个函数吗?其实我们可以使⽤lambda函数(匿名函数)。
Lambda 表达式
Lambda表达式就是只有⼀⾏的函数。⽐如下⾯这个lambda表达式可以求出给定数字的平⽅:
square = lambda x: x * x
运⾏下⾯的代码:
>>> square(3)9
你肯定在问:“参数去哪⼉了?这究竟是啥意思?看起来根本不像函数啊?”
嗯,的确是不太容易懂……但还是应该能够理解的。我们上⾯的代码把什么东西赋给了变量“square”。就是这个东西:
lambda x:
它告诉Python这是个lambda函数,输⼊的名字为x。冒号后⾯的⼀切都是对输⼊的操作,然后它会⾃
动返回操作的结果。
这样我们的求平⽅的代码可以简化成⼀⾏:
python新手代码例子x = [1, 2, 3, 4, 5]print(list(map(lambda num: num * num, x)))
有了lambda表达式,所有参数都放在左边,操作都放在右边。虽然看上去有点乱,但不能否认它的作⽤。实际上能写出只有懂得函数式编程的⼈才能看懂的代码还是有点⼩兴奋的。⽽且把函数变成⼀⾏也⾮常酷。
归纳
归纳(reduce)是个函数,它把⼀个可迭代对象变成⼀个东西。通常,我们在列表上进⾏计算,将列表归纳成⼀个数字。归纳的代码看起来长这样:
reduce(function, list)
上⾯的函数可以使⽤lambda表达式。
列表的乘积就是把所有数字乘到⼀起。可以这样写代码:
product = 1x = [1, 2, 3, 4]for num in x: product = product * num
但使⽤归纳,可以写成这样:
from functools import reduceproduct = reduce((lambda x, y: x * y),[1, 2, 3, 4])
这样能得到同样的结果。这段代码更短,⽽且借助函数式编程,这段代码更简洁。
过滤
过滤(filter)函数接收⼀个可迭代对象,然后过滤掉对象中⼀切不需要的东西。
通常过滤接收⼀个函数和⼀个列表。它会针对列表中的每个元素执⾏函数,如果函数返回True,则什么都不做。如果函数返回False,则从列表中去掉那个元素。
语法如下:
filter(function, list)
我们来看⼀个简单的例⼦。没有过滤,代码要写成这样:
x = range(-5, 5)new_list = []for num in x: if num < 0: new_list.append(num)
使⽤过滤可以写成这样:
x = range(-5, 5)all_less_than_zero = list(filter(lambda num: num < 0, x))
⾼阶函数
⾼阶函数接收函数作为参数,返回另⼀个函数。⼀个⾮常简单的例⼦如下所⽰:
def summation(nums): return sum(nums)def action(func, numbers): return func(numbers)print(action(summation, [1, 2, 3]))# Output is 6
或者更简单“返回函数”的例⼦:
def rtnBrandon(): return "brandon"def rtnJohn(): return "john"def rtnPerson(): age = int(input("What's your age?")) if age == 21: return rtnBrandon() else: return rt
还记得之前说过函数式编程语⾔没有变量吗?实际上⾼阶函数能很容易做到这⼀点。如果你只需要在⼀系列函数中传递数据,那么数据根本
不需要保存到变量中。
Python 中的所有函数都是顶级对象。顶级对象是拥有⼀个或多个以下特征的对象:
在运⾏时⽣成
赋值给某个数据结构中的变量或元素
作为参数传递给函数
作为函数的结果返回
所以,所有 Python 中的函数都是对象,都可以⽤作⾼阶函数。
部分函数
部分函数有点难懂,但⾮常酷。通过它,你不需要提供完整的参数就能调⽤函数。我们来看个例⼦。我们要创建⼀个函数,它接收两个参
数,⼀个是底,另⼀个是指数,然后返回底的指数次幂,代码如下:
def power(base, exponent): return base ** exponent
现在我们需要⼀个求平⽅的函数,可以这么写:
def square(base): return power(base, 2)
这段代码没问题,但如果需要⽴⽅函数怎么办?或者四次⽅函数呢?是不是得⼀直定义新的函数?这样做也⾏,但是程序员总是很懒的。如
果需要经常重复⼀件事情,那就意味着⼀定有办法提⾼速度,避免重复。我们可以⽤部分函数实现这⼀点。下⾯是使⽤部分函数求平⽅的例
⼦:
from functools import partialsquare = partial(power, exponent=2)print(square(2))# output is 4
这是不是很苦?我们事先告诉 Python 第⼆个参数,这样只需要提供⼀个参数就能调⽤需要两个参数的函数了。
还可以使⽤循环来⽣成直到能计算 1000 次⽅的所有函数。
from functools import partialpowers = []for x in range(2, 1001): powers.append(partial(power, exponent = x))print(powers[0](3))# output is 9
函数式编程不够 Python
你也许注意到了,我们这⾥许多函数式编程都⽤到了列表。除了归纳和部分函数之外,所有其他函数都⽣成列表。Guido(Python发明⼈)不
喜欢在 Python 中使⽤函数式的东西,因为 Python 有⾃⼰的⽅法来⽣成列表。
在 Python IDLE 中敲“import this”,可以看到下⾯的内容:
>>> import thisThe Zen of Python, by Tim PetersBeautiful is better than ugly.Explicit is better than implicit.Simple is better than complex.Complex is better than co 这就是Python之禅。这⾸诗表明了什么叫做Python风格。我们要指出的是这句话:
There should be one — and preferably only one — obvious way to do it.(任何事情应该有⼀个且只有⼀个⽅法解决。)
在 Python 中,映射和过滤能做到的事情,列表解析式(稍后介绍)也能做到。这就打破了 Python 之禅,因此我们说函数式编程的部分不够“Python”。
另⼀个常被提及的地⽅就是lambda。在Python中,lambda函数就是个普通的函数。lambda只是个语法糖。这两者是等价的:
foo = lambda a: 2def foo(a): return 2
普通的函数能做到⼀切 lambda 能做到的事情,但反过来却不⾏。lambda 不能完成普通函数能完成的⼀切事情。
关于为何函数式编程不适合Python⽣态系统曾有过⼀次讨论。你也许注意到,我之前提到了列表解析式,我们现在就来介绍下什么是列表解析式。
列表解析式
之前我说过,任何能⽤映射或过滤完成的事情都可以⽤列表解析式完成。这就是我们要学的东西。
列表解析式是 Python ⽣成列表的⽅式。语法如下:
[function for item in iterable]
要想求列表中每个数字的平⽅,可以这么写:
print([x * x for x in [1, 2, 3, 4]])
可以看到,我们给列表中的每个元素应⽤了⼀个函数。那么怎样才能实现过滤呢?先来看看之前的这段代码:
x = range(-5, 5)all_less_than_zero = list(filter(lambda num: num < 0, x))print(all_less_than_zero)
可以将它转换成下⾯这种使⽤列表解析式的⽅式:
x = range(-5, 5)all_less_than_zero = [num for num in x if num < 0]
像这样,列表解析式⽀持 if 语句。这样就不需要写⼀堆函数来实现了。实际上,如果你需要⽣成某种列表,那么很有可能使⽤列表解析式更⽅便、更简洁。
如果想求所有⼩于 0 的数字的平⽅呢?使⽤ Lambda、映射和过滤可以写成:
x = range(-5, 5)all_less_than_zero = list(map(lambda num: num * num, list(filter(lambda num: num < 0, x))))
看上去似乎很长,⽽且有点复杂。⽤列表解析式只需写成:
x = range(-5, 5)all_less_than_zero = [num * num for num in x if num < 0]
进:960410445 即可获取数⼗套PDF!
不过列表解析式只能⽤于列表。映射和过滤能⽤于⼀切可迭代对象。那为什么还要⽤列表解析式呢?
其实,解析式可以⽤在任何可迭代的对象上。
其他解析式
可以在任何可迭代对象上使⽤解析式。
任何可迭代对象都可以⽤解析式⽣成。从 Python 2.7 开始,甚⾄可以⽤解析式⽣成字典(哈希表)。
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论