函数式编程
- 函数式编程就是一种抽象程度很高的编程范式,纯粹的函数式编程语言编写的函数没有变量,因此,任意一个函数,只要输入是确定的,输出就是确定的,这种纯函数我们称之为没有副作用。而允许使用变量的程序设计语言,由于函数内部的变量状态不确定,同样的输入,可能得到不同的输出,因此,这种函数是有副作用的。
对应到编程语言,就是越低级的语言,越贴近计算机,抽象程度低,执行效率高,比如C语言;越高级的语言,越贴近计算,抽象程度高,执行效率低,比如Lisp语言。
函数式编程的一个特点就是,允许把函数本身作为参数传入另一个函数,还允许返回一个函数!
高阶函数
- 一个函数就可以接收另一个函数作为参数,这种函数就称之为高阶函数。
- 函数可以指向其他变量, f = abs --> f(-10) == 10
- 函数名其实就是指向函数的变量!对于abs()这个函数,完全可以把函数名abs看成变量,它指向一个可以计算绝对值的函数!
如果把abs指向其他对象,会有什么情况发生?
>>> abs = 10
>>> abs(-10)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'int' object is not callable
把abs指向10后,就无法通过abs(-10)调用该函数了!因为abs这个变量已经不指向求绝对值函数而是指向一个整数10!
当然实际代码绝对不能这么写,这里是为了说明函数名也是变量。要恢复abs函数,请重启Python交互环境。
注:由于abs函数实际上是定义在import builtins模块中的,所以要让修改abs变量的指向在其它模块也生效,要用import builtins; builtins.abs = 10。
常见高阶函数
- Note: 高阶函数经常和lambda一起配合使用哦。
- filter() — 过滤器,如果前面用函数,则返回function(item)中为true的结果,如果前面为None, 返回迭代项中为true的部分。filter()这个高阶函数,关键在于正确实现一个“筛选”函数。注意到filter()函数返回的是一个Iterator,也就是一个惰性序列,所以要强迫filter()完成计算结果,需要用list()函数获得所有结果并返回list
- 格式:filter(function or None, iterable) --> filter object --> 返回一个列表object–> list 查看
#筛选奇数,偶数的x % 2结果为0 就不会返回
list(filter(lambda x: x % 2, range(10)))
[1, 3, 5, 7, 9]
#求3的倍数, x%3 为0是if判断直接去else了,如果不为0就去False那边(这样filter就把它直接过滤了)
list(filter(lambda : False if x % 3 else x, range(100)))
# 去除序列中的空字符串, and 的用法,s为真时返回s.strip(), s为假时返回s本身然后被filter掉:)
>>> list(filter(lambda s: s and s.strip(), ['A', '', 'B', None, 'C', ' ']))
['A', 'B', 'C']
# 计算素数的一个方法是埃氏筛法,它的算法理解起来非常简单:
# 首先,列出从2开始的所有自然数,构造一个序列:
# 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, ...
# 取序列的第一个数2,它一定是素数,然后用2把序列的2的倍数筛掉:
# 3, 5, 7, 9, 11, 13, 15, 17, 19, ...
# 取新序列的第一个数3,它一定是素数,然后用3把序列的3的倍数筛掉:
# 5, 7, 8, 10, 11, 13, 14, 16, 17, 19, ...
# ...
# 不断筛下去,就可以得到所有的素数。
# 定义一个奇数[3,5,7,9...]的迭代器
def num_list():
n = 1
while True:
n += 2
yield n
# 定义素数的iterator
def primes():
yield 2 # 第一次读取返回2
g = num_list() # 初始化奇数列表的迭代器
while True:
num = next(g) # 每次取出该iterator的一个元素
yield num
g = filter(lambda x: x % num > 0, g) # lambda表达式每次将num以及其倍数通过filter去掉,再产生新的素数迭代器
p = primes()
# 给定范围来返回,因为素数iterator是全体素数,无限~
for each in p:
if each < 100:
print(each)
else:
break
- map() – 高阶函数之一,映射, 将每一个元素作为函数的参数进行运算加工,直到可迭代的每个元素都加工完毕,返回所有加工后的元素构成的新序列
- map(function or None, iterable)
- map()函数接收两个参数,一个是函数,一个是Iterable,map将传入的函数依次作用到序列的每个元素,并把结果作为新的Iterator返回。由于返回的迭代器是惰性的,需要用list把整个序列都计算出来。
eg.
list(map(lambda x: x*2, range(10)))
list(map(lambda x: x % 2, range(10)))
[0, 1, 0, 1, 0, 1, 0, 1, 0, 1]
- reduce() – 高阶函数,把一个函数作用在一个序列[x1, x2, x3, …]上,这个函数必须接收两个参数,reduce把结果继续和序列的下一个元素做累积计算,其效果就是:
reduce(f, [x1, x2, x3, x4]) = f(f(f(x1, x2), x3), x4)
但是如果要把序列[1, 3, 5, 7, 9]变换成整数13579,reduce就可以派上用场:
from functools import reduce
DIGITS = {
'1': 1,
'2': 2,
'3': 3,
'4': 4,
'5': 5,
'6': 6,
'7': 7,
'8': 8,
'9': 9,
'0': 0
}
# 方法一
'''
def str2int(s):
def fun(x, y):
return x 10 + y
def char2int(s):
return DIGITS[s]
return reduce(fun, map(char2int, s))
'''
# 方法二
def char2int(s):
return DIGITS[s]
def str2int(s):
return reduce(lambda x, y: x 10 + y, map(char2int, s))
print(str2int('13423'))
# 也就是说,假设Python没有提供int()函数,你完全可以自己写一个把字符串转化为整数的函数,而且只需要几行代码!
把“123.456” 字符串变为float 123.456
from functools import reduce
def char2float(s):
if not isinstance(s, str):
return
x = s.split('.')
return reduce(lambda a, b: a + b pow(10, -len(x[1])), map(int, x))
print(char2float('123.345'))
- sorted()(BIF)-- 返回一个全新的列表,不同于(列表的s.sort() 表示源列表被改), 可接受任何可迭代对象作为参数
对于不变对象来说,调用对象自身的任意方法,也不会改变该对象自身的内容。相反,这些方法会创建新的对象并返回,这样,就保证了不可变对象本身永远是不可变的。
sorted(s, reverse=True) -- 倒序
sorted(s, key=len) --- key是排序方法,这里引入len函数,无括号
s = ["peach", "pitaya", "durain", "dragon", "tangerine"]
sorted(s)
['dragon', 'durain', 'peach', 'pitaya', 'tangerine']
sorted(s, reverse = True)
['tangerine', 'pitaya', 'peach', 'durain', 'dragon']
sorted(s, key = len)
['peach', 'pitaya', 'durain', 'dragon', 'tangerine']
# 按照字母顺序,无视大小写
sorted(s, key = str.lower)
lambda 函数
- 创建匿名函数,更加精简, 一句话函数
lambda <参数1,参数2…> : <return 表达式>
eg:
def ds(x):
return x + 2
ds(3)
5
>>> lambda x : x + 2
<function <lambda> at 0x02F225C8> #产生的是一个函数,需要赋值使用
g = lambda x : x + 2 # 可以赋值给一个变量使用
g(3)
5
lambda表达式作用:
- 用python写一些之小脚本时,可以省下定义函数的过程,eg.只需要写个简单的脚本管理服务器时间,而不需要专门定义一个函数再写调用,使代码更简洁
- 对于一些比较抽象且整个程序执行下来只需调用一两次的函数,使用lambda不需要考虑函数命名的问题
- 简化代码的可读性,普通函数每次要去找def 部分,lambda可以省略这些
- 限制:就是只能有一个表达式,不用写return,返回值就是该表达式的结果。
装饰器(decorator)
- 现在,假设我们要增强now()函数的功能,比如,在函数调用前后自动打印日志,但又不希望修改now()函数的定义,这种在代码运行期间动态增加功能的方式,称之为“装饰器”(Decorator)。
>>> def now():
... print('2015-3-25')
本质上,decorator就是一个返回函数的高阶函数。所以,我们要定义一个能打印日志的decorator,可以定义如下:
def log(func):
def wrapper(*args, **kw):
print('call %s():' % func.__name__) # func.__name__ 表示函数的名称
return func(*args, **kw)
return wrapper
观察上面的log,因为它是一个decorator,所以接受一个函数作为参数,并返回一个函数。我们要借助Python的@语法,把decorator置于函数的定义处:
@log
def now():
print('2015-3-25')
调用now()函数,不仅会运行now()函数本身,还会在运行now()函数前打印一行日志:
>>> now()
call now():
2015-3-25
把@log放到now()函数的定义处,相当于执行了语句:
now = log(now)
由于log()是一个decorator,返回一个函数,所以,原来的now()函数仍然存在,只是现在同名的now变量指向了新的函数,于是调用now()将执行新函数,即在log()函数中返回的wrapper()函数。
wrapper()函数的参数定义是(*args, **kw),因此,wrapper()函数可以接受任意参数的调用。在wrapper()函数内,首先打印日志,再紧接着调用原始函数。
- 如果decorator本身需要传入参数,那就需要编写一个返回decorator的高阶函数,写出来会更复杂。比如,要自定义log的文本:
def log(text):
def decorator(func):
def wrapper(*args, **kw):
print('%s %s():' % (text, func.__name__))
return func(*args, **kw)
return wrapper
return decorator
这个3层嵌套的decorator用法如下:
@log('execute')
def now():
print('2015-3-25')
执行结果如下:
>>> now()
execute now():
2015-3-25
和两层嵌套的decorator相比,3层嵌套的效果是这样的:
>>> now = log('execute')(now)
我们来剖析上面的语句,首先执行log(‘execute’),返回的是decorator函数,再调用返回的函数,参数是now函数,返回值最终是wrapper函数。
以上两种decorator的定义都没有问题,但还差最后一步。因为我们讲了函数也是对象,它有__name__等属性,但你去看经过decorator装饰之后的函数,它们的__name__已经从原来的’now’变成了’wrapper’:
>>> now.__name__
'wrapper'
因为返回的那个wrapper()函数名字就是’wrapper’,所以,需要把原始函数的__name__等属性复制到wrapper()函数中,否则,有些依赖函数签名的代码执行就会出错。
不需要编写wrapper.name = func.__name__这样的代码,Python内置的functools.wraps就是干这个事的,所以,一个完整的decorator的写法如下:
import functools
def log(func):
@functools.wraps(func)
def wrapper(*args, **kw):
print('call %s():' % func.__name__)
return func(*args, **kw)
return wrapper
或者针对带参数的decorator:
import functools
def log(text):
def decorator(func):
@functools.wraps(func)
def wrapper(*args, **kw):
print('%s %s():' % (text, func.__name__))
return func(*args, **kw)
return wrapper
return decorator
eg: 设计一个decorator打印一个函数的执行时间
# -*- coding: UTF-8 -*-
import time, functools
# 不加参数装饰器
def metric(fn):
@functools.wraps(fn)
def wrapper(*args, **kw):
t1 = time.time()
result = fn(*args, **kw)
t2 = time.time()
print('function %s run %f ms' % (fn.__name__, t2 - t1))
return result
return wrapper
@metric
def fast(x, y):
time.sleep(0.0012)
return x + y
@metric
def slow(x, y, z):
time.sleep(0.1234)
return x y z
f = fast(11, 22)
s = slow(11, 22, 33)
print(f, s)
function fast run 0.006482 ms
function slow run 0.138556 ms
33 7986
# 装饰器加不加参数都可运行
# -*- coding: UTF-8 -*-
import time, functools
def log(text):
def decorator(fn):
@functools.wraps(fn)
def wrapper(*args, **kw):
if isinstance(text, str):
print('%s %s' % (text, fn.__name__))
else:
print('calling %s' % fn.__name__)
return fn(*args, **kw)
return wrapper
if isinstance(text, str):
return decorator
else:
return decorator(text)
@log
def fast(x, y):
time.sleep(0.0012)
return x + y
@log('execute')
def slow(x, y, z):
time.sleep(0.1234)
return x y z
f = fast(11, 22)
s = slow(11, 22, 33)
print(f, s)
偏函数(Partial Function)
- 偏函数(Partial function)来自functools.partial(func, *args, **kwargs)
- functools.partial的作用就是,把一个函数的某些参数给固定住(也就是设置默认值),返回一个新的函数,调用这个新函数会更简单。
- 作用:当函数的参数个数太多,需要简化时,使用functools.partial可以创建一个新的函数,这个新函数可以固定住原函数的部分参数,从而在调用时更简单
假设要转换大量的二进制字符串,每次都传入int(x, base=2)非常麻烦,于是,我们想到,可以定义一个int2()的函数,默认把base=2传进去:
>>> int('101101', base=2)
45
def int2(x, base=2):
return int(x, base)
这样,我们转换二进制就非常方便了:
>>> int2('1010101')
85
functools.partial就是帮助我们创建一个偏函数的,不需要我们自己定义int2(),可以直接使用下面的代码创建一个新的函数int2:
>>> import functools
>>> int2 = functools.partial(int, base=2)
>>> int2('1000000')
64
>>> int2('1010101')
85
注意到上面的新的int2函数,仅仅是把base参数重新设定默认值为2,但也可以在函数调用时传入其他值:
>>> int2('1000000', base=10)
1000000
最后,创建偏函数时,实际上可以接收函数对象、*args和**kw这3个参数,当传入:
int2 = functools.partial(int, base=2)
实际上固定了int()函数的关键字参数base,也就是:
int2('10010')
==>
kw = { 'base': 2 }
int('10010', **kw)
当传入:
max2 = functools.partial(max, 10)
实际上会把10作为*args的一部分自动加到左边,也就是:
max2(5, 6, 7)
相当于:
args = (10, 5, 6, 7)
max(*args)
结果为10。