Python高级特性,函数式编程

102 阅读9分钟

高级特性

切片

L = ['Michael', 'Sarah', 'Tracy', 'Bob', 'Jack'],取出list的前n个元素
r = []
n = 3
for i in range(n):
    r.append(L[i])
​
Python提供切片(Slice)操作符,取出前3个元素:L[0:3],取出前10个元素:L[:10]

迭代

Python的for循环可以用在listtupledict迭代示例:
for key in d: 迭代key,默认
for value in d.values(): 迭代value
for k,v in d.items(): 迭代key、value
​
字符串迭代: for ch in 'ABC' ==> A B C
判断对象是否可迭代: 
引入模块 from collections.abc import Iterable
API: isinstance(元素,Iterable) ==> True/False
​
如果要对list实现类似Java那样的下标循环,Python内置的enumerate函数可以把list编程索引-元素对,这样可以在for循环种同时迭代索引和元素本身:
for i,value in enumerate(['A','B','C']):
    print(i,value)
...
0 A
1 B
2 C

列表生成式

列表生成式即List Comprehensions, 是Python内置用来创建list的生成式
举个例子,要生成list [1,2,3,4,5,6,7,8,9],可以用list(range(1,11))
​
如果要生成[1x1, 2x2, 3x3, ..., 10x10]怎么做,方法一是循环:
L = []
for x in range(1,11):
    L.append(x * x)
​
用列表生成式: [x * x for x in range(1,11)]
用列表生成式筛选偶数的平方:[x * x for x in range(1,11) if x % 2 == 0]
​
还可以使用两层循环,生成全排列,三层循环慎用
[m + n for m in 'ABC' for n in 'XYZ']
==> ['AX', 'AY', 'AZ', 'BX', 'BY', 'BZ', 'CX', 'CY', 'CZ']
​
运用列表生成式,可以写出非常简洁的代码。例如,列出当前目录下的所有文件和目录名,可以通过一行代码实现:
>>> import os # 导入os模块,模块的概念后面讲到
>>> [d for d in os.listdir('.')] # os.listdir可以列出文件和目录for循环其实可以同时使用两个甚至多个变量,比如dict的items()可以同时迭代key和value:
>>> d = {'x': 'A', 'y': 'B', 'z': 'C' }
>>> for k, v in d.items():
...     print(k, '=', v)
...
y = B
x = A
z = C
​
因此,列表生成式也可以使用两个变量来生成list>>> d = {'x': 'A', 'y': 'B', 'z': 'C' }
>>> [k + '=' + v for k, v in d.items()]
['y=B', 'x=A', 'z=C']
​
最后把一个list中所有的字符串变成小写:
>>> L = ['Hello', 'World', 'IBM', 'Apple']
>>> [s.lower() for s in L]
['hello', 'world', 'ibm', 'apple']
​
if ... else
使用if: [x for x in range(1,11) if x % 2 == 0] ==> [2,4,6,8,10]
使用if else: [x if x % 2 == 0 else -x for x in range(1,11)] 
    ==> [-1, 2, -3, 4, -5, 6, -7, 8, -9, 10]

生成器

生成器Generator,generator保存的是算法,通过算法计算出下一个值
第一种:把列表生成式的[]改成()
L = [x * x for x in range(10)] ==> [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
g = (x * x for x in range(10)) ==> g就是个generator函数
可以使用next(g)获取下一个返回值,如果超出会报错StopIteration
​
generator函数也可以使用for循环
g = (x * x for x in range(10))
for n in g:
    print(n)
==> 0 1 4 9 16 25 36 49 64 81
​
斐波拉契数列
def fib(max):
    n, a, b = 0, 0, 1
    while n < max:
        print(b)
        a, b = b, a + b
        n = n + 1
    return 'done'
    
把print(b)改成yield b就成了generator函数
def fib(max):
    n, a, b = 0, 0, 1
    while n < max:
        yield b
        a, b = b, a + b
        n = n + 1
    return 'done'
    
这就是定义generator的另一种方法。如果一个函数定义中包含yield关键字,那么这个函数就不再是一个普通函数,而是一个generator函数,调用一个generator函数将返回一个generator
generator函数和普通函数的执行流程不一样。普通函数是顺序执行,遇到return语句或者最后一行函数语句就返回。而变成generator的函数,在每次调用next()的时候执行,遇到yield语句返回,再次执行时从上次返回的yield语句处继续执行。
​
注意:调用generator函数会创建一个generator对象,多次调用generator函数会创建多个相互独立的generator。

迭代器

能直接作用于for循环的数据类型有以下几种:
一类是:listtupledictsetstr
一类是generator,包括生成器和带yield的generator function
可以使用isinstance(对象,Iterable)判断对象是否是Iterable对象:
​
引入依赖 import collections.abc import Iterable
>>> isinstance([], Iterable)
True
>>> isinstance({}, Iterable)
True
>>> isinstance('abc', Iterable)
True
>>> isinstance((x for x in range(10)), Iterable)
True
>>> isinstance(100, Iterable)
False
​
但listdictstr不是Iterator

函数式编程

高阶函数

map() 、reduce()

map()函数接受两个参数,一个是函数,一个是Iterable,map将传入的函数一次作用到序列的每个元素,并把结果作为新的Iterator返回,举例说明,比如我们有一个函数f(x)=x2,要把这个函数作用在一个list [1, 2, 3, 4, 5, 6, 7, 8, 9]上,就可以用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]
​
把list中所有数字转为字符串
list(map(str,[1,2,3,4,5,6,7,8,9]))
​
​
reduce, reduce把一个函数作用在一个序列上[x1, x2, x3, ...]上,reduce函数必须接收两个参数,reduce把结果继续和序列的下一个元素做累积计算,其效果就是:
reduce(f, [x1, x2, x3, x4]) = f(f(f(x1,x2), x3), x4)
比方说对一个序列求和,可用reduce实现:
>>> from functools import reduce
>>> def add(x, y):
...     return x + y
...
>>> reduce(add, [1, 3, 5, 7, 9])
25
​
当然,求和我们一般用python的sum()函数,没必要自己写
更复杂的reduce()用法,可以在实践中学习,感觉有点绕

filter

Python内建函数filter()函数用于过滤序列,相当于Java里stream流的filter
​
例如,在一个list中,删掉偶数,只保留奇数,可以这么写:
def is_odd(n):
    return n % 2 == 1
    
list(filter(is_odd, [1, 2, 4, 5, 6, 9, 10, 15]))
# 结果: [1, 5, 9, 15]
​
把一个序列中的空字符串删掉,可以这么写:
def not_empty(s):
    return s and s.strip()
​
list(filter(not_empty, ['A', '', 'B', None, 'C', '']))
==> ['A', 'B', 'C']

sorted

sorted()函数,对集合进行排序
​
sorted()函数可以接收list: 
>>> sorted([36, 5, -12, 9, -21])
[-21, -12, 5, 9, 36]
​
也可以携带排序规则: 
>>> sorted([36, 5, -12, 9, -21], key=abs) 
[5, 9, -12, -21, 36]
​
对字符串按照字母排序:
>>> sorted(['bob', 'about', 'Zoo', 'Credit'])
['Credit', 'Zoo', 'about', 'bob']
​
这是因为ASCII的大小比较,'Z'排在'a'前面,我们可以忽略大小写再进行排序:
>>> sorted(['bob', 'about', 'Zoo', 'Credit'], key=str.lower)
['about', 'bob', 'Credit', 'Zoo']
​
如果想要反向排序,传入第三个参数reverse=True>>> sorted(['bob', 'about', 'Zoo', 'Credit'], key=str.lower, reverse=True)
['Zoo', 'Credit', 'bob', 'about']
​
给一组tuple表示的学生名字和成绩排序:
L = [('Bob', 75), ('Adam', 92), ('Bart', 66), ('Lisa', 88)]
​
根据名字排序:
print(sorted(L, key=lambda x: x[0]))
根据成绩排序:
print(sorted(L, key=lambda x: x[1], reverse=True))

返回函数

高阶函数除了可以接受函数作为参数外,还可以把函数作为结果值返回
比如求和的函数是这样的:
def calc_sum(*args):
    sum = 0
    for n in args:
        sum += n
    return sum
​
如果不想立即求和,在后面的代码中根据需要再计算,那么可以返回函数
def lazy_sum(*args):
    def sum():
        sum = 0
        for n in args:
            sum += n
        return sum
    return sum
​
当我们调用lazy_sum()函数时,返回的是求和函数
>>> f = lazy_sum(1, 3, 5, 7, 9)
>>> f
<function lazy_sum.<locals>.sum at 0x101c6ed90>
调用函数f时,才真正计算求和结果
>>>f()
25
​
注意:每次调用lazy_sum()时,都会返回一个新的函数
​
闭包
f1 = lazy_sum(1,2,3,4)
f2 = lazy_sum(1,2,3,4)
如果lazy_sum()返回的函数其定义内部引用了局部变量args,会被所有的创建的函数所共享,如f1、f2
灵活运用的话这里比较深奥难懂,可以自行看网上课程
返回闭包时牢记一点:返回函数不要引用任何循环变量,或者后续会发生变化的变量。

匿名函数

请先看个例子,计算f(x) = x2:
>>> list(map(lambda x: x * x, [1, 2, 3, 4, 5, 6, 7, 8, 9]))
[1, 4, 9, 16, 25, 36, 49, 64, 81]
​
以上案例就是用了lambda的匿名函数,lambda x:x*x,实际上它是
def f(x):
    return x*x
​
匿名函数的限制:只能有一个表达式,不用写return,返回值就是该表达式的结果
匿名函数的好处:因为没有名字,不用担心函数名冲突
​
可以把匿名函数作为返回值返回,比如:
def build(x,y):
    return lambda: x*x +y*y

装饰器

Decorator,如同java的装饰器,Python的装饰器是一种增强能力的设定,以下是日志打印函数
def now():
    print('2015-3-25')
​
我们想增强now()函数的功能,同事不希望修改now()函数,那么可以采用装饰器Decorator
def log(func):
    def wrapper(*args, **kw):
        print('call %s():' % func.__name__)
        return func(*args, **kw)
    return wrapper
​
decorator可以用Python的@语法,置于函数的定义处:
@log
def now():
    print('2015-03-25')
    
>>> now()
call now():
2015-03-25
​
但此时打印的数据是固定死的,如果需要打印自定义文本,那么需要从外界传参,还需要顶一个更高阶的函数
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('excutor')
def now():
    print('2015-03-25')
执行结果如下:
>>> now()
excute now():
2015-03-25

偏函数

Python的functools模块提供的偏函数(Partial function),用来简化函数的参数
比如字符串转换成各个进制的数字,int()默认为十进制:
>>> int('12345')
12345int()函数还提供了base参数以供选择进制
>>> int('12345', base=8)
5349
>>> int('12345', 16)
74565
​
如果每次都传入base参数我们觉得麻烦,那么可以定义一个int2()函数,把默认的base=2传进去
def int2(x,base=2):
    return int(x,base)
​
functools.partial就是帮助我们创建一个函数的,使用functools.partial()语法创建函数:
引入依赖: import functools
>>> int2 = functools.partial(int, base=2)
>>> int2('1000000')
64