Python进阶

生成器(Generators)

首先得了解三个概念

  • 可迭代对象(Iterable)
  • 迭代器(Iterator)
  • 迭代(Iteration)

可迭代对象(Iterable)

Python中任意的对象,只要它定义了可以返回一个迭代器的__iter__方法,或者定义了可以支持下标索引的__getitem__方法(这些双下划线方法会在其他章节中全面解释),那么它就是一个可迭代对象。简单说,可迭代对象就是能提供迭代器的任意对象。

迭代器(Iterator)

任意对象,只要定义了next(Python2) 或者__next__方法,它就是一个迭代器。

迭代(Iteration)

用简单的话讲,它就是从某个地方(比如一个列表)取出一个元素的过程。当我们使用一个循环来遍历某个东西时,这个过程本身就叫迭代。

生成器(Generators)

生成器也是一种迭代器,但是你只能对其迭代一次。这是因为它们并没有把所有的值存在内存中,而是在运行时生成值。你通过遍历来使用它们,要么用一个for循环,要么将它们传递给任意可以进行迭代的函数和结构。大多数时候生成器是以函数来实现的。然而,它们并不返回一个值,而是yield(暂且译作生出)一个值

def generator_function():
    for i in xrange(10):
        yield i

for i in generator_function():
    print i

许多Python2里的标准库函数都会返回列表,而Python3都修改成了返回生成器,因为生成器占用更少的资源。

下面写一个斐波拉契的的函数,使用生成器。

def fb(n):
    a,b = 0,1
    for i in xrange(n):
        yield a
        a,b = b, a + b

print fb(10)

那么Python中的那些数据类型支持迭代呢?我们看一下str

some_str = "hello world"
next(some_str)

你就会发现直接报错,因为str不是一个迭代器,而是一个迭代对象。所以它支持迭代,但不支持迭代操作。有个内置函数:iter,可以式str变成一个迭代器。

str_iter = iter(some_str)
next(str_iter)

函数编程

高阶函数

在函数式编程中,我们可以将函数当作变量一样自由使用。一个函数接收另一个函数作为参数,这种函数称之为高阶函数(Higher-order Functions)

def func(f, arr):
    return [f(x) for x in arr]

上面的代码中,func 是一个高阶函数,它接收两个参数,第 1 个参数是函数,第 2 个参数是数组,func 的功能是将函数 g 逐个作用于数组 arr 上,并返回一个新的数组,比如,我们可以这样用:

def double(x):
    return 2 * x

def square(x):
    return x * x

arr1 = func(double, [1, 2, 3, 4])
arr2 = func(square, [1, 2, 3, 4])

匿名函数(lambda)

lambda 参数: 表达式

关键字lambda说明它是一个匿名函数,冒号:前面的变量是该匿名函数的参数,冒号后面是函数的返回值,注意这里不需使用return关键字。

square = lambda x:x*x
print square(10)

lambda的使用场景,函数一般适用于创建一些临时性的,小巧的函数。

map/reduce/filter

map函数

map(function, sequence)

sequence中的item依次执行function(item),并将结果组成一个List返回,也就是:

[function(item1), function(item2), function(item3), ...]

def square(n):
    return n*n

print map(square,[1,2,3,4])

reduce

reduce(function, sequence[, initial])

先将sequence的前两个item传给function,即function(item1, item2),函数的返回值和sequence的下一个item再传给function,即function(function(item1, item2), item3),如此迭代,直到sequence没有元素,如果有initial,则作为初始值调用。

reduece(f, [x1, x2, x3, x4]) = f(f(f(x1, x2), x3), x4)

reduce(lambda x, y: x * y, [1, 2, 3, 4])  # 相当于 ((1 * 2) * 3) * 4
reduce(lambda x, y: x * y, [1, 2, 3, 4], 5) # ((((5 * 1) * 2) * 3)) * 4

filter

filter(function, sequnce)

function依次作用于sequnce的每个item,即function(item),将返回值为Tru 的 item 组成一个List/String/Tuple(取决于sequnce的类型,python3统一返回迭代器) 返回。

even_num = list(filter(lambda x: x % 2 == 0, [1, 2, 3, 4, 5, 6]))
filter(lambda x: x < 'g', 'hijack')

装饰器(Decorator)

在Python中

  • 函数可以被赋值给其他变量
  • 函数可以被删除
  • 可以在函数里面再定义函数
  • 函数可以作为参数传递给另外一个函数
  • 函数可以作为另一个函数的返回

简而言之,函数就是一个对象。

对一个简单的函数进行装饰

def hello():
    return 'hello world'

def makeitalic(func):
    def wrapped():
        return "<i>" + func() + "</i>"
    return wrapped

hello = makeitalic(hello)
print hello()

在上面,我们将hello函数传给makeitalic,再将返回赋给hello,此时调用hello()就得到了我们想要的结果。

事实上,makeitalic就是一个装饰器(decorator),我们可以使用@来简化上面的写法。

def makeitalic(func):
    def wrapped():
        return "<i>" + func() + "</i>"
    return wrapped

@makeitalic
def hello():
    return 'hello world'

像上面的情况,可以动态修改函数(或类)功能的函数就是装饰器。本质上,它是一个高阶函数,以被装饰的函数(比如上面的 hello)为参数,并返回一个包装后的函数(比如上面的 wrapped)给被装饰函数(hello)

装饰器的副作用

使用装饰器有一个瑕疵,就是被装饰的函数,它的函数名称已经不是原来的名称了,回到最开始的例子:

def makeitalic(func):
    def wrapped():
        return "<i>" + func() + "</i>"
    return wrapped

@makeitalic
def hello():
    return 'hello world'

函数hellomakeitalic装饰后,它的函数名称已经改变了:

>>> hello.__name__
'wrapped'

为了消除这样的副作用,Python 中的functool包提供了一个wraps的装饰器:

from functools import wraps

def makeitalic(func):
    @wraps(func)       # 加上 wraps 装饰器
    def wrapped():
        return "<i>" + func() + "</i>"
    return wrapped

@makeitalic
def hello():
    return 'hello world'

>>> hello.__name__
'hello'