函数有副作用: 对于同一个函数相同的输入, 产生的结果是不一样的
函数无副作用: 上述的前提下函数输出是一样的
函数式编程就是一种抽象程度很高的编程范式,纯粹的函数式编程语言编写的函数没有变量
函数式编程的一个特点就是,允许把函数本身作为参数传入另一个函数,还允许返回一个函数!
1 高阶函数
变量可以指向函数, 也就是函数本身也可以复制给变量
实际上函数名本身也是变量
将函数作为参数传给另一个函数, 这样的函数就是高阶函数
1.1 map和reduce
map有两个参数, 一个是函数, 另一个是迭代器
>>> def f(x):
... return x * x
...
>>> r = map(f, [1, 2, 3, 4, 5, 6, 7, 8, 9])
>>> list(r)
[1, 4, 9, 16, 25, 36, 49, 64, 81]
reduce把一个函数作用在一个序列上, 这个函数必须接受两个参数, reduce会先把序列的前两个参数传入函数计算,返回的结果会继续和下一个参数继续进行运算,直到完成整个序列的运算
>>> from functools import reduce
>>> def add(x, y):
... return x + y
...
>>> reduce(add, [1, 3, 5, 7, 9])
25
其效果等同于1+3+5+7+9
>>> from functools import reduce
>>> def fn(x, y):
... return x * 10 + y
...
>>> def char2num(s):
... return {'0': 0, '1': 1, '2': 2, '3': 3, '4': 4, '5': 5, '6': 6, '7': 7, '8': 8, '9': 9}[s]
...
>>> reduce(fn, map(char2num, '13579'))
13579
该程序可以整个为
from functools import reduce
def str2int(s):
def fn(x, y):
return x * 10 + y
def char2num(s):
return {'0': 0, '1': 1, '2': 2, '3': 3, '4': 4, '5': 5, '6': 6, '7': 7, '8': 8, '9': 9}[s]
return reduce(fn, map(char2num, s))
最后还可以用lambda函数优化
from functools import reduce
def char2num(s):
return {'0': 0, '1': 1, '2': 2, '3': 3, '4': 4, '5': 5, '6': 6, '7': 7, '8': 8, '9': 9}[s]
def str2int(s):
return reduce(lambda x, y: x * 10 + y, map(char2num, s))
1.2 filter
filter用于过滤, 会传入两个参数, 一个是函数, 一个是序列. 函数只会处理一个参数且函数返回值是True或者False.当返回值是True的时候该值保留下来, 当返回值是False的时候该参数将不会被保留.
def not_empty(s):
return s and s.strip()
list(filter(not_empty, ['A', '', 'B', None, 'C', ' ']))
# 结果: ['A', 'B', 'C']
1.3 sorted
sorted是用于排序的.
当排序的序列中是数字的时候, 直接对数字进行排序. 当序列中的内容是非数字的时候排序就不能以大小判断了. 此时可以传入一个参数key来限定排序方法.
>>> sorted([36, 5, -12, 9, -21], key=abs)
[5, 9, -12, -21, 36]
默认情况下, 字母的排序是按照ASCII的排序关系排序
要进行反向排序, 需要加入第三个参数, reverse=True
>>> sorted(['bob', 'about', 'Zoo', 'Credit'], key=str.lower)
['about', 'bob', 'Credit', 'Zoo']
2 返回函数
函数可以做为对象赋值给变量, 因而返回函数实际上就是将函数封装传出去了
例如想要做一个求和的函数, 但是并不希望调用该函数执行之后就返回结果, 而是在想要使用的时候调用.
def lazy_sum(*args):
def sum():
ax = 0
for n in args:
ax = ax + n
return ax
return sum
可以看到 lazy_sum()函数返回的就是sum函数.当返回值执行函数的时候才进行计算
>>> f = lazy_sum(1, 3, 5, 7, 9)
>>> f
<function lazy_sum.<locals>.sum at 0x101c6ed90>
>>> f()
25
3 闭包
闭包(Closure)就是传出返回函数, 传出的函数在执行的时候可以调用原函数内容的变量. 类似于上述lazy_sun的情况.
闭包的基本形式是:
在函数F1中, 定义F2, F2只能引用F1定义的变量, 之后F1函数返回F2的函数名字
def count():
fs = []
for i in range(1, 4):
def f():
return i*i
fs.append(f)
return fs
f1, f2, f3 = count()
此时关键来了, 当执行f1, f2, f3的时候返回的结果是..
>>> f1()
9
>>> f2()
9
>>> f3()
9
原因是由于闭包的特性是在具体调用的时候去取原函数中变量的值. 然而在返回fs的时候会走count()中的代码, 在具体函数执行之前i值已经是i=3了.
因此在闭包的时候, 不能引用任何循环变量.
如果真的需要引用这个循环变量的话, 就需要将循环变量绑定到一个函数的参数当中.
def count():
def f(j):
def g():
return j*j
return g
fs = []
for i in range(1, 4):
fs.append(f(i)) # f(i)立刻被执行,因此i的当前值被传入f()
return fs
具体还可以通过lambda表达式优化上述代码
def count():
def f(j):
def g():
return j*j
return g
return [f(i) for i in range(1,4)]
f1, f2, f3 = count()
4 匿名函数
匿名函数用于一些操作不必要比较短小没必要写成函数的方式
>>> list(map(lambda x: x * x, [1, 2, 3, 4, 5, 6, 7, 8, 9]))
[1, 4, 9, 16, 25, 36, 49, 64, 81]
匿名函数只能是一个表达式, 且不用写return, 返回值就是表达式的结果.
常用的匿名函数用lambda表达式.
写法:
lambda 参数 : 参数的运算式 (返回的结果是参数的表达式的结果)
lambda 参数表达式 (返回的值就是表达式的计算结果)
5 装饰器
装饰器(Decorator), 在不修改原函数的情况下, 在代码运行期间动态增加功能的方式
def log(func):
def wrapper(*args, **kw):
print('call %s():' % func.__name__)
return func(*args, **kw)
return wrapper
@log
def now():
print('2017-3-28')
>>> now()
call now():
2017-3-28
此处的@log相当于执行了
now = log(now)
另外定义装饰器的时候, 可以用 @装饰器(参数) 装饰器上添加参数传入, 对此原有的装饰器需要在外层再加一层函数来传入参数, 因为如果只在刚才的最外层添加是无法做到准确传入传入的参数的
def log(text):
def decorator(func):
def wrapper(*args, **kw):
print('%s %s():' % (text, func.__name__))
return func(*args, **kw)
return wrapper
return decorator
@log('execute')
def now():
print('2015-3-25')
此处的@log('execute')相当于执行了
now = log('execute')(now)
但是这样的装饰器还是有一个问题就是:
每个函数对象都有一个__name__属性, 这个属性保存了函数的名字. 但是执行完装饰器之后, 该属性值__name__会被覆盖成别的名字.改进方法是:
import functools
def log(func):
@functools.wraps(func)
def wrapper(*args, **kw):
print('call %s():' % func.__name__)
return func(*args, **kw)
return wrapper
对于带参数的装饰器有:
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
6 偏函数
偏函数是 functools.partial模块里, 通过固定某个函数的某个参数来达到形成新函数简化操作的目的.
通过functools.partial可以传入原来的函数名字, 设定的默认参数的值, 来返回一个新的函数更加方便的定义并且使用
诸如:
int(要转化的参数, base进制)
当我们要定义直接转化二进制的时候可以
int2 = functools.partial(int, base=2)
具体的
>>> import functools
>>> int2 = functools.partial(int, base=2)
>>> int2('1000000')
64
>>> int2('1010101')
85