Python的几个特性高效指令, since 2022-04-07

(2022.04.07 Thu)
用了下面方法,你的代码更Pythonic。

列表解析list comprehension

以紧凑的方式对list/tuple/dict的一系列元素进行处理,处理结果被保存为原格式。
list的基本形式

[ for i in iterator]

比如保存0到9的平方值到一个列表中

>> result = [i**2 for i in range(10)]
>> print(result)
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]

其中也可以加入条件判断

>> result = [i**2 for i in range(10) if i%2==0]
>>> print(result)
[0, 4, 16, 36, 64]

dict的基本形式

{k:v for k,v in iterator}

keyvalue这两个列表中的值分别作为key和value赋给一个dict

>> key = ['name', 'gender', 'age']
>> value = ['john', 'm', 18]
>> ad = {k: v for k, v in zip(key, value)}

其中的zip函数功能是将两个list/tuple按index结合成pair的tuple,将在本文后面解释。

匿名函数lambda

匿名函数是简便的定义函数的方式,形式如

lambda :

如计算某个值的9次方

>> s = lambda x: x**9
>> s(2)
512

计算多个元素的函数

>> k = lambda x, y: x*5 + y*7
>>> k(4, 5)
55

函数式编程 - map、filter、reduce

以Python3中的用法为例

map指令用于对list/tuple中的各个元素执行相同操作,返回结果非list
格式

map(function, object)

>> a = map(lambda x: x**2, [1, 2, 3])
>> print(list(a))
[1, 4, 9]

filter用于对执行对象中,进行指定操作,结果中符合True条件/非0的对象的初始值,做为返回结果。格式和map相同。返回结果非list

>> a = filter(lambda x: x-10, [9, 10, 11])
>> print(list(a))
[9, 11]
>> b = filter(lambda x:x*x-4,range(10))
>> print(list(b))
[0, 1, 3, 4, 5, 6, 7, 8, 9]

reduce的作用对象是Python中的可迭代结构(iterable),如list tuple array。执行如下操作
1 将指定函数应用于可迭代结构的前若干项,产生部分结果
2 用部分结果,和可迭代结构中的下一项,继续使用指定函数,产生中间结果
3 重复上面过程直到iterable结构耗尽(exhausted),并生成一个累计结果。

reduce在python3里面已经移到了functools里面

>> from functools import reduce
>> reduce(lambda x,y: x+y, [i for i in range(10)])
45

生成器generator

(2022.05.05 Thu)

生成器的创建有两种方式,生成器函数和生成器表达式。

生成器表达式

和列表解析(list comprehension)相似,生成式表达式可以用简短的语句生成一个生成器对象(generator object)。不同于列表解析中使用中括号[],生成器表达式使用小括号()。

生成器表达式的优势在于迭代之前不需要在内存中创建和保存对象的所有元素,因而节省了大量内存。作为代价,每次从生成器中获取元素都需要做计算,提升了计算时间。

考虑下面两个例子。用生成器对象化和列表解析分别计算相同的序列。结果可知,生成器对象占用的空间远小于列表解析但是利用生成器对象计算sum花费了数倍于列表解析的时间

import sys, cProfile
>>> s1 = '(c for c in range(10000))'
>>> s2 = '[c for c in range(10000)]'
# 查看两个指令占用的内存
>>> sys.getsizeof(eval(s1))
112
>>> sys.getsizeof(eval(s2))
85176
# 查看两个表达式sum的运行时间
>>> cProfile.run('sum'+s1)
         10005 function calls in 0.004 seconds

   Ordered by: standard name

   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
    10001    0.002    0.000    0.002    0.000 :1()
        1    0.000    0.000    0.004    0.004 :1()
        1    0.000    0.000    0.004    0.004 {built-in method builtins.exec}
        1    0.002    0.002    0.004    0.004 {built-in method builtins.sum}
        1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects}
>>> cProfile.run('sum('+s2+')')
         5 function calls in 0.001 seconds

   Ordered by: standard name

   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
        1    0.001    0.001    0.001    0.001 :1()
        1    0.000    0.000    0.001    0.001 :1()
        1    0.000    0.000    0.001    0.001 {built-in method builtins.exec}
        1    0.000    0.000    0.000    0.000 {built-in method builtins.sum}
        1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects}

生成器函数

生成器函数使用yield关键字,并且用yield关键字代替一般函数中的return关键字。考虑如下这个生成器函数isequence

def isequence():
    num = 0 
    while True:
        print('num before yield: ', num)
        yield num
        num += 1
        print('num after yield: ', num)

该函数中yield num语句表明变量num的值会返回给调用者caller。与普通的函数在return处结束不同,在调用yield之后不会退出该函数,而是保存函数状态。当next()方法调用生成器对象,被生成的值num执行+=操作并继续生成。当一个函数中有多个yield命令时,则在调用者调用next()方法时依次轮流暂停。

下面是函数的调用结果。可以看到在第一次使用next(b)命令后,返回结果num before yield: 0,但没有打印num before yield:。说明函数状态保存至yield num这条命令,而后面的操作直到下一次调用next(b)命令时才会被执行,参考第二次调用next(b)的结果。第二次调用next(b)指令后,先返回的是num after yield: 1后返回了num before yield: 1

>>> b = isequence()
>>> next(b)
num before yield:  0
0
>>> next(b)
num after yield:  1
num before yield:  1
1

(2022.06.14 Tues)
生成器的应用之一是生成Fibonacci数列,下面这个例子返回0和1开始的Fibonacci数列,根据用户指定的值返回之前所有的值。

def fs(num):
    cntr, t1 = 1, 0
    yield (cntr, t1)
    cntr += 1
    t2 = t1 + 1
    yield (cntr, t2)
    cntr = 3 
    while cntr <= num:
        # print('counter=', cntr)
        yield (cntr, t1 + t2)
        cntr += 1
        t1, t2 = t2, t1+t2

调用

>> fibs = fs(10)
>> type(fibs)

>> list(fibs)
[(1, 0), (2, 1), (3, 1), (4, 2), (5, 3), (6, 5), (7, 8), (8, 13), (9, 21), (10, 34)]

生成器的send方法

我们在生成器函数中将yield命令赋值给变量,并查看变量

def gensend():
    res, num = [], 0
    while True:
        tmp = yield num # yield表达式赋值
        num += 1
        res.append(tmp)
        print('num: ', num)
        print('tmp: ', tmp)
        print('res: ', res)

next方法调用查看结果。看到yield语句赋值的变量tmp其值默认为None,而该函数的执行顺序如前所述,在执行next命令是保存yield语句所在行为止的函数状态,待下次调用next命令时继续执行函数余下部分。

>>> d = gensend()
>>> next(d)
0
>>> next(d)
num:  1
tmp:  None
res:  [None]
1

下面引入生成器的send方法。当send方法被调用,产生两个效果:

  • 生成器函数返回值给调用者caller
  • 调用者传入值给生成器函数

如上面案例使用send方法传入值给生成器函数。观察返回结果,可发现,tmp = yield numnum值已经返回给调用者,而该语句中的tmp也接受了调用者通过send方法传递到生成器函数的值。

>>> d.send(9)
num:  2
tmp:  9
res:  [None, 9]
2

对比next方法和send方法:next方法仅能从生成器函数接收结果,而send方法不仅可以接收,还可以传递值给yield对应的表达式对应的变量。

但是,在第一次调用生成器函数时如果使用send方法传递参数只能传递None

>>> k = gensend()
>>> k.send(1)
Traceback (most recent call last):
  File "", line 1, in 
TypeError: can't send non-None value to a just-started generator
>>> k.send(None)
0

生成器的throwclose方法

placeholder

Reference

1 realpython - introduction to Python generator

你可能感兴趣的:(Python的几个特性高效指令, since 2022-04-07)