Python3 函数式编程(高阶函数)

这一次主要是学习了一下Python3函数式编程思想,介绍了3个代表性高阶函数:map(), reduce(), filter()。像 sorted() 其实也是高阶函数,可以接受函数作为参数。这篇学习笔记中编写了大量高阶函数,同时介绍了Python中的闭包,装饰器。这些思想和方法很美妙,我受益匪浅。当然这些都需要进一步运用和学习。

运行环境:Python3.6 + Jupyter notebook

 

函数式编程

 

函数作为参数

In [1]:
def add(x, y, f):
    return f(x) + f(y)
In [2]:
add(-1, 1, abs)
Out[2]:
2
 

map

 

map()作为高阶函数,它将运算规则抽象了。

In [3]:
r = map(lambda x: x*x, [1, 2, 3, 4])
list(r)
Out[3]:
[1, 4, 9, 16]
 

reduce

In [5]:
from functools import reduce
def prod(List,f):
    return reduce(f, List)
def f(x, y):
    return x * y
prod([1,2,3], f)
Out[5]:
6
 

str2int

In [6]:
from functools import reduce
num = {}
for i in range(10):
    num[str(i)] = i
    
def str2int(string:str) -> int:
    return reduce(lambda x, y: x*10 + y, map(char2num, string))
def char2num(s):
    return num[s]
In [7]:
str2int('123')
Out[7]:
123
In [8]:
type(str2int('123'))
Out[8]:
int
 

str2float

In [13]:
from functools import reduce
num = {}
for i in range(10):
    num[str(i)] = i

def char2num(s):
    return num[s]
def str2float(string):
    s = string.split('.')
    f1 = lambda x, y: x * 10 + y
    f2 = lambda x, y: x * 0.1 + y
    return reduce(f1, map(char2num, s[0])) + 0.1 * reduce(f2, map(char2num, s[-1][::-1]))
In [14]:
str2float('1.234')
Out[14]:
1.234
In [15]:
type(str2float('1.234'))
Out[15]:
float
 

filter

 

利用filter() 求素数(埃氏筛法)

In [22]:
def odd_iter():
    n = 1
    while True:
        n += 2
        yield n
In [23]:
def prime():
    yield 2
    it = odd_iter()
    while True:
        n = next(it)
        yield n
        it = filter(lambda x: x % n > 0, it)
In [24]:
for i in prime():
    if i < 20:
        print(i, end=' ')
    else:
        break
 
2 3 5 7 9 11 13 15 17 19 
 

返回函数

In [25]:
def lazy_sum(*args):
    def sum():
        ax = 0
        for arg in args:
            ax += arg
        return ax
    return sum
In [26]:
f = lazy_sum(1, 2, 3)
f
Out[26]:
<function __main__.lazy_sum.<locals>.sum>
In [27]:
f()
Out[27]:
6
 

我们在函数lazy_sum中又定义了函数sum,并且,内部函数sum可以引用外部函数lazy_sum的参数和局部变量,当lazy_sum返回函数sum时,相关参数和变量都保存在返回的函数中,这种称为“闭包(Closure)”的程序结构拥有极大的威力。

 

闭包

In [29]:
def count():
    fs = []
    for i in range(1, 4):
        def f():
            return i * i
        fs.append(f)
    return fs
In [30]:
f1, f2, f3 = count()
In [32]:
f1(), f2(), f3()
Out[32]:
(9, 9, 9)
 

注:闭包返回的函数并没有立刻执行,而是直到调用了f()才执行。

 

<font color = red>返回函数不要包含任何循环变量,或者后续会发生变化的变量。</font>

 

如果一定要引入循环变量呢?方法是在 count() 中调用 f(),从而绑定循环变量当前值。

In [55]:
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
In [57]:
f1, f2, f3 = count()
In [58]:
f1(), f2(), f3()
Out[58]:
(1, 4, 9)
 

example

In [88]:
def creat_counter():
    def g():
        n = 1
        while True:
            yield n
            n += 1
    it = g()
    def counter():
        return next(it)
    return counter
In [89]:
counterA = creat_counter()
In [90]:
counterA()
Out[90]:
1
In [91]:
counterA()
Out[91]:
2
 

注:creat_counter() 作为一个计数器函数。

 

转换简单的类为闭包

In [92]:
class sample:
    def __init__(self,value):
        self.value = value
    def func(self):
        print('n = ', self.value)
    def get_value(self):
        return self.value
    def set_value(self,new_value):
        self.value = new_value
In [93]:
test = sample(1)
test.func()
 
n =  1
In [94]:
def sample(n):
    value = n
    def func():
        print('n = ', value)
    def get_value():
        return value
    def set_value(new_value):
        nonlocal value
        value = new_value
    
    func.get_value = get_value
    func.set_value = set_value
    return func
In [95]:
test = sample(1)
In [96]:
test()
 
n =  1
In [97]:
test.get_value()
Out[97]:
1
In [98]:
test.set_value(5)
test()
 
n =  5
 

注:将 get_value() 和 set_value() 作为函数 func()的属性,然后利用闭包返回 func() 函数

 

参考:访问闭包中定义的变量,里面有进一步内容。

 

装饰器

In [99]:
def now():
    print('2018/2/20')
In [100]:
now.__name__
Out[100]:
'now'
 

现在希望增加 now() 函数的功能,但不改变他的定义,这种在代码运行期间动态增加功能的方式,称之为装饰器(Decorator)。

 

本质上,decorator 就是一个返回函数的高阶函数。下面,我们定义一个打印日志的 decorator。

In [101]:
def log(func):
    print('call {}'.format(func.__name__))
In [102]:
log(now)
 
call now
 

但是这样就显式调用了 log(),说白了是两个函数

In [104]:
def log(func):
    def wrapper(*args, **kw):
        print('call {}'.format(func.__name__))
        return func(*args, **kw)
    return wrapper
In [105]:
now = log(now)
In [106]:
now()
 
call now
2018/2/20
 

以上就实现了打印日志的 now() 函数,同时会调用 now() 函数实现功能。

In [107]:
@log
def now():
    print('2018/2/20')
In [108]:
now()
 
call now
2018/2/20
 

注:@log 相当于执行语句:

now = log(now)

 

log()就是一个 decorator,它返回 wrapper() 函数,执行 @log 后,wrapper 赋值给 now,再次执行 now() 后,我们会执行 wrapper() 函数,打印日志,然后执行原来的 now() 函数。

 

下面进一步。自定义日志内容

 

大致思想是下面两条语句:

now = log('text')(now)

now()

写一个3层嵌套的 decorator:log('text') 返回一个函数,返回相当于上一个例子的 log() 函数。

In [109]:
def log(text):
    def decorator(func):
        def wrapper(*args, **kw):
            print('{0}{1}():'.format(text, func.__name__))
            return func(*args, **kw)
        return wrapper
    return decorator
In [111]:
def now():
    print('2018/2/20')
now = log('execute')(now)
now()
 
execute now():
2018/2/20
 

用法如下:

In [112]:
@log('execute')
def now():
    print('2018/2/20')
now()
 
execute now():
2018/2/20
 

但是有一个问题:

In [113]:
now.__name__
Out[113]:
'wrapper'
 

由于最后返回的是 wrapper 函数,因此 now 的 __name__ 属性就是 'wrapper'。

 

完整代码:

 

例一:

In [114]:
import functools
def log(func):
    @functools.wraps(func)
    def wrapper(*args, **kw):
        print('call {}'.format(func.__name__))
        return func(*args, **kw)
    return wrapper

@log
def now():
    print('2018/2/20')
In [115]:
now()
 
call now
2018/2/20
In [116]:
now.__name__
Out[116]:
'now'
 

例二:

In [117]:
import functools
def log(text):
    def decorator(func):
        @functools.wraps(func)
        def wrapper(*args, **kw):
            print('{0}{1}():'.format(text, func.__name__))
            return func(*args, **kw)
        return wrapper
    return decorator

@log('execute')
def now():
    print('2018/2/20')
In [118]:
now()
 
execute now():
2018/2/20
In [119]:
now.__name__
Out[119]:
'now'
 

最后,写一个打印函数执行时间的decorator

In [139]:
import time, functools
def meric(func):
    @functools.wraps(func)
    def wrapper(*args, **kw):
        start = time.time()
        result = func(*args, **kw)
        end = time.time()
        print('{} executed in {} ms'.format(func.__name__, end - start))
        return result
    return wrapper
In [140]:
@meric
def test(x, y):
    time.sleep(0.1)
    return x + y
In [141]:
test(1,2)
 
test executed in 0.10009908676147461 ms
Out[141]:
3
 
posted @ 2018-02-20 16:03  orange1002  阅读(2044)  评论(0编辑  收藏  举报