这篇博客中每一行代码都值得反复学习。
函数是Python内建支持的一种封装,我们通过把大段代码拆成函数,通过一层一层的函数调用,就可以把复杂任务分解成简单的任务,这种分解可以称之为面向过程的程序设计。 函数就是面向过程的程序设计的基本单元。
参考连接
全文整理自廖雪峰大佬的函数式编程教程
何为函数式编程
函数式编程 (请注意多了一个“式”字)——Functional Programming,虽然也可以归结到面向过程的程序设计,但其思想更接近数学计算。
函数式编程与命令式编程最大的不同其实在于: 函数式编程关心数据的映射,命令式编程关心解决问题的步骤 这里的映射就是数学上「函数」的概念,即一种东西和另一种东西之间的对应关系。 这也是为什么「函数式编程」叫做「函数」式编程,核心思想就是要代码体现这种映射关系。
举个例子
假如,现在你来面试,面试官让你把二叉树镜像反转一下?
命令式编程:描述「从旧树得到新树应该怎样做」来实现
def invertTree (root ): if root is None : return None root.left, root.right = invertTree(root.right), invertTree(root.left) return root
好了,现在停下来看看这段代码究竟代表着什么—— 它的含义是:首先判断节点是否为空;然后翻转左树;然后翻转右树;最后左右互换。 这就是命令式编程——你要做什么事情,你得把达到目的的步骤详细的描述出来,然后交给机器去运行。 这也正是命令式编程的理论模型——图灵机的特点。一条写满数据的纸带,一条根据纸带内容运动的机器,机器每动一步都需要纸带上写着如何达到。
函数式编程:通过描述一个 旧树->新树 的映射来实现
def invert (node ): if node is None : return None else return Tree(node.value, invert(node.right), invert(node.left))
所谓“翻转二叉树”,可以看做是要得到一颗和原来二叉树对称的新二叉树。这颗新二叉树的特点是每一个节点都递归地和原树相反。这段代码体现的思维,就是旧树到新树的映射——对一颗二叉树而言,它的镜像树就是左右节点递归镜像的树。 Haskell(发音为/ˈhæskəl/)是一种标准化的,通用的纯函数编程语言,有非限定性语义和强静态类型。它的命名源自美国逻辑学家哈斯凯尔·加里,他在数理逻辑方面上的工作使得函数式编程语言有了广泛的基础。在Haskell中,“函数是第一类对象”。作为一门函数编程语言,主要控制结构是函数。Haskell语言是1990年在编程语言Miranda的基础上标准化的,并且以 \(\lambda\) 演算为基础 发展而来。这也是为什么Haskell语言以希腊字母 \(\lambda\) (Lambda)作为自己的标志。Haskell具有 "证明即程序、命题为类型" 的特征。
那么这样有什么好处呢?
首先,最直观的角度来说,函数式风格的代码可以写得很精简 ,大大减少了键盘的损耗???
函数式编程的一个特点就是,允许把函数本身作为参数传入另一个函数,还允许返回一个函数!
其次,函数式的代码是“对映射的描述”,它不仅可以描述二叉树这样的数据结构之间的对应关系,任何能在计算机中体现的东西之间的对应关系都可以描述 ——比如函数和函数之间的映射(比如 functor );比如外部操作到 GUI 之间的映射(就是现在前端热炒的所谓 FRP)。它的抽象程度可以很高,这就意味着函数式的代码可以更方便的复用。 抽象程度越高越容易被复用,离计算机硬件越近,离人类越远。
另外还有其他答主提到的,可以方便的并行 。
同时,将代码写成这种样子可以方便用数学的方法进行研究(不能理解 monad 就是自函子范畴上的一个幺半群你还想用 Haskell 写出 Hello world ?)
至于什么科里化、什么数据不可变,都只是外延体现而已。
那么这样处理有什么坏处?
在处理I/O时,要么引入可变变量,要么通过Monad来进行封装(如State Monad和IO Monad)。单纯依靠函数式编程是不行的。
Python对函数式编程提供部分支持。由于Python允许使用变量,因此,Python不是纯函数式编程语言。
来源:知乎 nameoverflow
Python中的高阶函数
map/reduce
相传Google Search的技术栈有三驾马车,分别是Google File System、BigTable、MapReduce[1],这三种技术共同支撑起了Google search business的江山,MapReduce实际上就是一个分布式的计算框架。
[1] Dean J , Ghemawat S . MapReduce: Simplified Data Processing on Large Clusters[C]// Sixth Symposium on Operating System Design & Implementation. USENIX Association, 2004.
map函数 :接受一个键值对(key-value pair)(文本名:文本内容),产生一组 中间键值对(单词:出现次数)。MapReduce框架会将map函数产生的中间键值对里键相同的值传递给一个reduce函数。
reduce函数 :接受一个键key,以及相关的一组值(value list)(单词A:(1,2,3)),将这组值进行合并产生一组规模更小的值(通常只有一个或零个值)(单词A:6)。
map
map
到底在干啥?\(map(func, (1, 2, 3)) = (func(1), func(2), func(3))\)
map()
函数接收两个参数,一个是函数,一个是Iterable
,map
将传入的函数依次作用到序列的每个元素,并把结果作为新的 Iterator
返回A。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 print("基于map函数生成 生成器" )def f (x ): return x * x iterabel = [1 , 2 , 3 ] _generator = map (f, iterabel) print(_generator) print("可迭代:{}" .format ('__iter__' in dir (_generator))) print("可生成:{}" .format ('__next__' in dir (_generator))) print(next (_generator))for i in _generator: print(i) print("基于()生成 生成器" ) _generator2 = (x*x for x in [1 , 2 , 3 ]) print(_generator2) print("可迭代:{}" .format ('__iter__' in dir (_generator2))) print("可生成:{}" .format ('__next__' in dir (_generator2))) print(next (_generator2))for i in _generator2: print(i) print("基于yiled生成 生成器" )def fun (nums ): index = 0 while index < len (nums): x = nums[index] yield x * x index += 1 _generator3 = fun([1 , 2 , 3 ]) print(_generator3) print("可迭代:{}" .format ('__iter__' in dir (_generator3))) print("可生成:{}" .format ('__next__' in dir (_generator3))) print(next (_generator3))for i in _generator3: print(i) 基于map 函数生成 生成器 <map object at 0x000001C7AFF25520 > 可迭代:True 可生成:True 1 4 9 基于()生成 生成器 <generator object <genexpr> at 0x000001C7AFEA92E0 > 可迭代:True 可生成:True 1 4 9 基于yiled生成 生成器 <generator object fun at 0x000001C7AFEA9350 > 可迭代:True 可生成:True 1 4 9
map()
传入的第一个参数是f
,即函数对象本身。由于结果r
是一个Iterator
,Iterator
是惰性序列,因此通过list()
函数让它把整个序列都计算出来并返回一个 list
。
可以发现 ,通过 map
函数返回得到的是一个生成器和通过 ()
生成一个生成器、基于yiled
生成一个生成器的效果是一样的。
map()
作为高阶函数,事实上它把运算规则抽象了,因此,我们不但可以计算简单的 \(f(x)=x^2\) ,还可以计算任意复杂的函数,比如,把这个 list
所有数字转为字符串:
>>> list (map (str , [1 , 2 , 3 , 4 , 5 , 6 ])) ['1' , '2' , '3' , '4' , '5' , '6' ]>>> [str (x) for x in [1 , 2 , 3 , 4 , 5 , 6 ]] ['1' , '2' , '3' , '4' , '5' , '6' ]
可以发现 ,map
也可以完成列表解析式的工作。
reduce
reduce
到底在干啥?\(reduce(func, (1, 2, 3, 4, 5))=func(func(func(func(1, 2), 3), 4), 5)\)
reduce
函数和map
函数一样,也接收两个参数,一个是函数,一个是Iterable
,reduce
函数把一个函数func
作用在一个序列[x1, x2, x3, ...]
上,这个 func
函数必须接收两个参数,reduce
把结果继续和序列的下一个元素做累积计算 ,其效果就是:
reduce(f, [x1, x2, x3, x4]) = f(f(f(x1, x2), x3), x4)
一个简单的例子,把序列[1, 3, 5, 7, 9]
变换成整数13579
,reduce
就可以派上用场:
from functools import reducedef list2num (x, y ): return x * 10 + y print(reduce(list2num, [1 , 2 , 3 ]))
注意,装饰器中的 wraps
函数也在 functools
模块中,wraps
函数负责将原始函数的__name__
等属性复制到wrapper()
函数中,否则,有些依赖函数签名的代码执行就会出错。
reduce
和 map
结合,将str转换成int,map
将str转换成list,reduce
将list转换成int。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 from functools import reducedef str2list (x ): digit = {'0' :0 , "1" : 1 , "2" : 2 , "3" : 3 , "4" : 4 , "5" : 5 , "6" : 6 , "7" : 7 , "8" : 8 , "9" : 9 } return digit[x]def list2num (x, y ): return x * 10 + y step1 = map (str2list, "123" ) res = reduce(list2num, step1) res = reduce(list2num, map (str2list, "123" )) def str2int (_str ): def list2num (x, y ): return x * 10 + y def str2list (x ): digit = {'0' :0 , "1" : 1 , "2" : 2 , "3" : 3 , "4" : 4 , "5" : 5 , "6" : 6 , "7" : 7 , "8" : 8 , "9" : 9 } return digit[x] return reduce(list2num, map (str2list, _str)) res = str2int("12345" ) print(type (res)) print(res)
practice
利用map()
函数,把用户输入的不规范的英文名字,变为首字母大写,其他小写的规范名字。输入:['adam', 'LISA', 'barT']
,输出:['Adam', 'Lisa', 'Bart']
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 def format (_name ): _name = list (_name) if ord (_name[0 ]) > 90 : _name[0 ] = chr (ord (_name[0 ])-32 ) for i in range (1 , len (_name)): if ord (_name[i]) < 97 : _name[i] = chr (ord (_name[i])+32 ) return '' .join(_name) names = ['adam' , 'LISA' , 'barT' ] a = map (format , names) print(list (a)) ['Adam' , 'Lisa' , 'Bart' ]
Python提供的sum()
函数可以接受一个list并求和,请编写一个prod()
函数,可以接受一个list并利用reduce()
求积:
from functools import reducedef prod (nums ): def product (x, y ): return x * y return reduce(product, nums) print('3 * 5 * 7 * 9 =' , prod([3 , 5 , 7 , 9 ]))if prod([3 , 5 , 7 , 9 ]) == 945 : print('测试成功!' )else : print('测试失败!' )3 * 5 * 7 * 9 = 945 测试成功!
利用map
和reduce
编写一个str2float
函数,把字符串'123.456'
转换成浮点数123.456
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 from functools import reduce is_decimal = False carry = 1 def numstr2list (x ): digit = {'0' :0 , "1" : 1 , "2" : 2 , "3" : 3 , "4" : 4 , "5" : 5 , "6" : 6 , "7" : 7 , "8" : 8 , "9" : 9 , "." : "." } return digit[x]def list2float (x, y ): if y == '.' : global is_decimal is_decimal = True return x if is_decimal: global carry carry /= 10 return x + y * carry else : return x * 10 + ydef str2float (_nums ): return reduce(list2float, list (map (numstr2list, '123.456' ))) res = str2float('123.456' ) print('str2float(\'123.456\') =' , res)if abs (res - 123.456 ) < 0.00001 : print('测试成功!' )else : print('测试失败!' ) str2float('123.456' ) = 123.456 测试成功!
filter
基本原理
filter()
函数用于过滤序列。\(filter(func, (1, 2, 3))=(1\ if\ func(1),\ 2\ if\ func(2),\ 3\ if\ func(3))\)
和map()
类似,filter()
也接收一个函数和一个序列。和map()
不同的是,filter()
把传入的函数依次作用于每个元素,然后根据返回值是True
还是False
决定保留还是丢弃该元素。
例如,在一个list中,删掉偶数,只保留奇数,可以这么写:
nums = [1 , 2 , 3 , 4 , 5 ] res = [x for x in nums if x%2 != 0 ] print(res) [1 , 3 , 5 ]
这是列表解析器的写法,使用 filter()
函数,可以这么写:
def is_odd (x ): return x % 2 != 0 def is_enve (x ): return x % 2 == 0 nums = [1 , 2 , 3 , 4 , 5 ] res = filter (is_odd, nums) print(type (res)) print('__next__' in dir (res)) print(res) <class 'filter '>True [1, 3, 5]
practice
只用 filter
求素数
计算素数 的一个方法是埃氏筛法 ,它的算法理解起来非常简单:
首先,列出从2
开始的所有自然数,构造一个序列: 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, ... 取序列的第一个数2
,它一定是素数,然后用2
把序列的2
的倍数筛掉: 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, ... 取新序列的第一个数3
,它一定是素数,然后用3
把序列的3
的倍数筛掉: 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, ... 取新序列的第一个数5
,然后用5
把序列的5
的倍数筛掉: 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, ... 不断筛下去,就可以得到所有的素数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 def _odd_iter (): n = 1 while True : n = n + 2 yield ndef _not_divisible (n ): return lambda x: x % n > 0 def primes (): yield 2 it = _odd_iter() while True : n = next (it) yield n it = filter (_not_divisible(n), it) _max = 100 res = []for n in primes(): if n < _max: res += [n] else : break print(" " .join([str (x) for x in res]))2 3 5 7 11 13 17 19 23 29 31 37 41 43 47 53 59 61 67 71 73 79 83 89 97
同时用filter
、map
、lambda
求100以内的素数。
a = [item for item in filter ( lambda x:all ( map ( lambda p: x % p !=0 , range (2 , x) ) ), range (2 , 101 ) ) ] a [2 , 3 , 5 , 7 , 11 , 13 , 17 , 19 , 23 , 29 , 31 , 37 , 41 , 43 , 47 , 53 , 59 , 61 , 67 , 71 , 73 , 79 , 83 , 89 , 97 ]
回数是指从左向右读和从右向左读都是一样的数,例如12321
,909
。请利用filter()
筛选出回数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 from functools import reducedef int2list (_int ): res = [] while _int: res.insert(0 , _int % 10 ) _int //= 10 return resdef is_palindrome (nums ): nums = int2list(nums) if len (nums) == 1 : return True if len (nums) == 2 and nums[0 ] == nums[1 ]: return True copy_nums = nums[:] copy_nums.reverse() return copy_nums == numsdef _is_palindrome (n ): m = int (str (n)[::-1 ]) return m == n output = filter (is_palindrome, range (1 , 1000 )) print('1~1000:' , list (output))if list (filter (is_palindrome, range (1 , 200 ))) == [1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 , 11 , 22 , 33 , 44 , 55 , 66 , 77 , 88 , 99 , 101 , 111 , 121 , 131 , 141 , 151 , 161 , 171 , 181 , 191 ]: print('测试成功!' )else : print('测试失败!' ) 1 ~1000 : [1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 , 11 , 22 , 33 , 44 , 55 , 66 , 77 , 88 , 99 , 101 , 111 , 121 , 131 , 141 , 151 , 161 , 171 , 181 , 191 , 202 , 212 , 222 , 232 , 242 , 252 , 262 , 272 , 282 , 292 , 303 , 313 , 323 , 333 , 343 , 353 , 363 , 373 , 383 , 393 , 404 , 414 , 424 , 434 , 444 , 454 , 464 , 474 , 484 , 494 , 505 , 515 , 525 , 535 , 545 , 555 , 565 , 575 , 585 , 595 , 606 , 616 , 626 , 636 , 646 , 656 , 666 , 676 , 686 , 696 , 707 , 717 , 727 , 737 , 747 , 757 , 767 , 777 , 787 , 797 , 808 , 818 , 828 , 838 , 848 , 858 , 868 , 878 , 888 , 898 , 909 , 919 , 929 , 939 , 949 , 959 , 969 , 979 , 989 , 999 ] 测试成功!
sorted
Python内置的sorted()
也是一个高阶函数。用sorted()
排序的关键在于实现一个映射函数。
>>> sorted ([36 , 5 , -12 , 9 , -21 ]) [-21 , -12 , 5 , 9 , 36 ]>>> sorted ([36 , 5 , -12 , 9 , -21 ], reverse=True ) [36 , 9 , 5 , -12 , -21 ]
作为一个高阶函数,sorted可以接收一个key
函数来实现自定义的排序,key指定的函数将作用于list的每一个元素上,并根据key函数返回的结果进行排序。对比原始的list和经过key=abs
处理过的list,例如按绝对值大小排序:
>>> sorted ([36 , 5 , -12 , 9 , -21 ], key=abs ) [5 , 9 , -12 , -21 , 36 ]
对比原始的 list 和经过key=abs
处理过的 list:
>>> [abs (x) for x in _list] [36 , 5 , 12 , 9 , 21 ]>>> _list [36 , 5 , -12 , 9 , -21 ]
字符串排序
>>> sorted (['bob' , 'about' , 'Zoo' , 'Credit' ]) ['Credit' , 'Zoo' , 'about' , 'bob' ]
默认情况下,对字符串排序,是按照ASCII的大小比较的,由于'Z' < 'a'
,结果,大写字母Z
会排在小写字母a
的前面。 str.lower()
方法将字符串中所有大写字符转换为小写字符 。str.upper()
方法将字符串中所有小写字符转换为大写字符 。
>>> sorted (['bob' , 'about' , 'Zoo' , 'Credit' ], key=str .lower) ['about' , 'bob' , 'Credit' , 'Zoo' ]
practice
假设我们用一组tuple表示学生名字和成绩:
L = [('Bob' , 75 ), ('Adam' , 92 ), ('Bart' , 66 ), ('Lisa' , 88 )]
请用sorted()
对上述列表分别按名字排序:
L = [('Bob' , 75 ), ('Adam' , 92 ), ('Bart' , 66 ), ('Lisa' , 88 )]def func (item ): return item[0 ] res = sorted (L, key=func) print(res) [('Adam' , 92 ), ('Bart' , 66 ), ('Bob' , 75 ), ('Lisa' , 88 )]
再按成绩从高到低排序:
def by_score (t ): return -t[1 ] L2 = sorted (L, key=by_score) print(L2)def by_score (t ): return t[1 ] L2 = sorted (L, key=by_score, reverse=True ) print(L2) [('Adam' , 92 ), ('Lisa' , 88 ), ('Bob' , 75 ), ('Bart' , 66 )]
一个保存学生姓名和成绩的字典,根据成绩对学生进行排序:
d = {'Bob' : 75 , 'Adam' : 92 , 'Bart' : 66 , 'Lisa' : 88 }def by_score (item ): return -item[1 ] d2 = sorted (d.items(), key=by_score,) print(d2) [('Adam' , 92 ), ('Lisa' , 88 ), ('Bob' , 75 ), ('Bart' , 66 )]
从1-100中选出所有的素数
a = [str (item) for item in filter (lambda x: all (map (lambda p: x % p != 0 , range (2 , x))), range (2 , 101 ))]
返回函数(闭包)
高阶函数除了可以接受函数作为参数外,还可以把函数作为结果值返回。
在外部函数outter_func
中又定义了内部函数inner_func
,并且,内部函数inner_func
可以引用外部函数outter_func
的参数和局部变量 ,当outter_func
返回inner_func
时,相关参数和变量都保存在返回的函数中 ,这种程序结构被称为“闭包(Closure)”,拥有极大的威力。
举个栗子
通常情况下,求和的函数是这样定义的:
def calc_sum (*args ): ax = 0 for n in args: ax = ax + n return ax
如果想惰性求和,怎么办?
def lazy_sum (*args ): def sum (): ax = 0 for n in args: ax = ax + n return ax return sum
当我们调用lazy_sum()
时,返回的并不是求和结果,而是求和函数,执行求和函数时,才真正开始求和:
>>> f = lazy_sum(1 , 2 , 3 , 4 , 5 , 6 )>>> f <function lazy_sum.<locals >.sum at 0x0000021C61BB0160 >>>> f()21
在这个例子中,我们在函数lazy_sum
中又定义了函数sum
,并且,内部函数sum
可以引用外部函数lazy_sum
的参数和局部变量,当lazy_sum
返回函数sum
时,相关参数和变量都保存在返回的函数中,这就是典型的“闭包(Closure)”程序结构。
请再注意一点,当我们调用lazy_sum()
时,每次调用都会返回一个新的函数 ,即使传入相同的参数:
>>> f1 = lazy_sum(1 , 3 , 5 , 7 , 9 )>>> f2 = lazy_sum(1 , 3 , 5 , 7 , 9 )>>> f1==f2False
f1()
和f2()
的调用结果互不影响。 函数在其定义内部引用了局部变量args
,所以,当一个函数返回了一个函数后,其内部的局部变量还被新函数引用。返回的函数并没有立刻执行,而是直到调用了f()
才执行。
def count (): fs = [] for i in range (1 , 4 ): def f (): return i * i fs.append(f) return fs f1, f2, f3 = count()
在上面的例子中,每次循环,都创建了一个新的函数,然后,把创建的3个函数都返回了。 你可能认为调用f1()
,f2()
和f3()
结果应该是1
,4
,9
,但实际结果是:
>>> f1, f2, f3 = count()>>> f1()9 >>> f2()9 >>> f3()
全部都是9
!原因就在于返回的函数引用了变量i
,但它并非立刻执行。等到3个函数都返回时,它们所引用的变量i
已经变成了3
,因此最终结果为9
。 返回闭包时牢记一点:返回函数不要引用任何循环变量,或者后续会发生变化的变量。 如果一定要引用循环变量怎么办?方法是再创建一个函数,用该函数的参数绑定循环变量当前的值,无论该循环变量后续如何更改,已绑定到函数参数的值不变:
def count (): def f (j ): def g (): return j * j return g fs = [] for i in range (1 , 4 ): fs.append(f(i)) return fs f1, f2, f3 = count()
再看看结果:
>>> f1, f2, f3 = count()>>> f1()1 >>> f2()4 >>> f3()9
缺点 是代码较长,可利用lambda函数缩短代码。
practice
利用闭包返回一个计数器函数,每次调用它返回递增整数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 def createCounter (): i = [0 ] def counter (): i[0 ] += 1 return i[0 ] return counter counterA = createCounter() print(counterA(), counterA(), counterA(), counterA(), counterA()) counterB = createCounter()if [counterB(), counterB(), counterB(), counterB()] == [1 , 2 , 3 , 4 ]: print('测试通过!' )else : print('测试失败!' )1 2 3 4 5 测试通过!
小结
一个函数可以返回一个计算结果,也可以返回一个函数。 返回一个函数时,牢记该函数并未执行,返回函数中不要引用任何可能会变化的变量。
匿名函数
当我们在传入函数时,有些时候,不需要显式地定义函数,直接传入匿名函数更方便。(Python3好像是不推荐使用了)
在Python中,对匿名函数提供了有限支持。还是以map()
函数为例,计算\(f(x)=x^2\) 时,除了定义一个f(x)
的函数外,还可以直接传入匿名函数:
>>> list (map (lambda x: x*x, [1 , 2 , 3 , 4 , 5 ])) [1 , 4 , 9 , 16 , 25 ]
关键字lambda
表示匿名函数,冒号前面的x
表示函数参数。
匿名函数有个限制,就是只能有一个表达式,不用写return
,返回值就是该表达式的结果。
用匿名函数有个好处,因为函数没有名字,不必担心函数名冲突。此外,匿名函数也是一个函数对象,也可以把匿名函数赋值给一个变量,再利用变量来调用该函数:
>>> f = lambda x: x*x>>> f <function <lambda > at 0x0000020778980160 >>>> type (f) <class 'function '>>>> list (map (f, [1 , 2 , 3 , 4 , 5 ] ) ) [1, 4, 9, 16, 25]
同样,也可以把匿名函数作为返回值返回,比如:
def build (x, y ): return lambda : x * x + y * y
practice
请用匿名函数改造下面的代码:
def is_odd (n ): return n % 2 == 1 L = list (filter (lambda x: x%2 == 1 , range (1 , 20 )))
改造完成
def is_odd (n ): return n % 2 == 1 L = list (filter (lambda x: x%2 == 1 , range (1 , 20 ))) [1 , 3 , 5 , 7 , 9 , 11 , 13 , 15 , 17 , 19 ]
装饰器
在面向对象(OOP)的设计模式中,decorator被称为装饰模式。OOP的装饰模式需要通过继承和组合来实现,而Python除了能支持OOP的decorator外,直接从语法层次支持decorator。Python的decorator可以用函数实现,也可以用类实现。 decorator可以增强函数的功能,定义起来虽然有点复杂,但使用起来非常灵活和方便。 详解参看这篇文章
偏函数
简单总结functools.partial
的作用就是,把一个函数的某些参数给固定住(也就是设置默认值),返回一个新的函数,调用这个新函数会更简单。
Python的functools
模块提供了很多有用的功能,其中一个就是偏函数(Partial function)。要注意,这里的偏函数和数学意义上的偏函数不一样。
在介绍函数参数的时候,我们讲到,通过设定参数的默认值,可以降低函数调用的难度。而偏函数也可以做到这一点。举例如下:
int()
函数可以把字符串转换为整数,当仅传入字符串时,int()
函数默认按十进制转换:
但 int()
函数还提供额外的 base
参数,默认值为 10
。如果传入 base
参数,就可以做 N
进制的转换:
>>> int ("12345" , base=10 ) 12345 >>> int ("12345" , base=8 ) 5349 >>> int ("12345" , base=16 ) 74565 >>> int ("1011" , base=2 ) 11
假设要转换大量的二进制字符串,每次都传入int(x, base=2)
非常麻烦,于是,我们想到,可以定义一个int2()
的函数,默认把base=2
传进去:
def int2 (x, base=2 ): return int (x, base)
这样,我们转换二进制就非常方便了:
>>> int2('1000000' )64 >>> int2('1010101' )85
functools.partial
就是帮助我们创建一个偏函数的,不需要我们自己定义int2()
,可以直接使用下面的代码创建一个新的函数int2
:
>>> import functools>>> int2 = functools.partial(int , base=2 )>>> int2('1011' )11
所以,简单总结functools.partial
的作用就是,把一个函数的某些参数给固定住(也就是设置默认值),返回一个新的函数,调用这个新函数会更简单。
注意到上面的新的int2
函数,仅仅是把base
参数重新设定默认值为2
,但也可以在函数调用时传入其他值:
>>> int2('1000000' , base=10 )1000000
最后,创建偏函数时,实际上可以接收函数对象、*args和**kw 这3个参数 ,当传入:
int2 = functools.partial(int , base=2 )
实际上固定了int()函数的关键字参数base
,也就是:
相当于:
kw = { 'base' : 2 }int ('10010' , **kw)
当传入:
min3 = functools.partial(min , 3 )
实际上会把3
作为*args
的一部分自动加到左边 ,也就是:
相当于:
args = (3 , 5 , 6 , 7 ) min3(*args)
结果为3
,这样就能控制返回的最小结果不小于 3
了。
小结
当函数的参数个数太多,需要简化时,使用functools.partial
可以创建一个新的函数,这个新函数可以固定住原函数的部分参数,从而在调用时更简单。