Loading

Python知识查漏补缺

1.Python传参之 * /

Keyword-only argument 是一种只能通过关键字指定而不能通过位置指定的参数。这迫使调用者必须指明,这个值是传给哪一个参数的。在函数的参数列表中,这种参数位于 * 符号的右侧。
Positional-only argument 是这样一种参数,它不允许调用者通过关键字来指定,而是要求必须按位置传递。这可以降低调用代码与参数名称之间的耦合程度。在函数的参数列表中,这些参数位于 / 符号的左侧。
在参数列表中, 位于 / 与 * 之间的参数,可以按位置指定,也可以用关键字来指定。这也是Python普通参数的默认指定方式。
注:以上特性由python3.8引入

2.Python推导式的使用原则

常见的有列表推导式、字典推导式
列表推导式:

a = [1, 2, 3, 4, 5, 6, 7, 8, 9]
squares = [i**2 for i in a]

even_squares = [i**2 for i in a if i % 2 == 0]

一些原则:

  1. 控制推导逻辑的子表达式不要超过两个
matrix = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
flat_matrix = [i for row in matrix for i in row]
  1. 推导的时候每层循环可以使用多个if条件,如果这些if条件在同一层循环内,那么他们之间默认是and关系。
  2. 考虑使用生成器表达式改写数据量较大的列表推导
it = (len(i) for i in range(100000))

返回的it为一个迭代器,可以使用next(it)取得其中的数据
当然,生成器表达式还有另外一个强大的功能,就是可以组合使用,比如使用刚才生成器表达式返回的it作为输入,编写一条新的生成器表达式:

roots = ((x, x**0.5 for x in it))

这条生成器表达式形成的roots迭代器每次遍历时候,会引发连锁反应:它也推进内部迭代器it以判断是否还能在it上继续迭代,如果可以,就把it所返回的值传入(x, x**0.5)里面求出结果。
注:需要注意的是生成器表达式返回的生成器即迭代器是有状态的,跑完一轮之后,就不能继续使用了,需要重新生成才能再次使用。
4. 通过yield from 把多个生成器连接起来使用
多个生成器连续使用时候一般我们可以这么做:
例子:编写一个图形程序,让他在屏幕上移动,从而形成动画效果,假设实现这么一段动画,图片先快速移动一段时间,然后暂停,接下来慢速移动一段时间,为了把移动和暂停表示出来,可以定义下面两个生成器函数,让他们分别给出图片在当前时间内时间段内应该保持的速度

def move(period, speed):
    for _ in range(period):
        yield speed
def pause(period):
    for _ in range(period):
        yield 0
def animate():
    for delta in move(4, 5.0):
        yield delta
    for delta in pause(3):
        yield delta
    for delta in move(2, 3.0):
        yield delta
def render(delta):
    print(f'Delta: {delta:.1f}')
def run(func):
    for delta in func():
        render(delta)
if __name__ == '__main__':
    run(animate)
# Delta: 5.0
# Delta: 5.0
# Delta: 5.0
# Delta: 5.0
# Delta: 0.0
# Delta: 0.0
# Delta: 0.0
# Delta: 3.0
# Delta: 3.0

这种写法问题在于,animate函数里有很多重复的地方,代码比较繁琐。解决此问题可以使用yield from来实现

def animate_composed():
  yield from move(4, 5.0)
  yield from pause(3)
  yield from move(2, 3.0)

run(animate_composed)
# 结果相同

使用yield from的好处在于代码看上去更清晰、直观。同时yield from的速度更快,相比较普通yield语句的for循环速度
接下来使用python内置的timeit模块编写运行一个micro-benchmark来测试下两者速度差别:

import timeit
def child():
    for i in range(1_000_000):
        yield i

def slow():
    for i in child():
        yield i

def fast():
    yield from child()

def run_comparison():
    baseline = timeit.timeit(
        stmt='for _ in slow(): pass',
        globals=globals(),
        number=50
    )
    print(f'Manual nesting {baseline:.2f}s')
    comparison = timeit.timeit(
        stmt='for _ in fast(): pass',
        globals=globals(),
        number=50
    )
    print(f'Composed nesting {comparison:.2f}s')
    reduction = -(comparison - baseline) / baseline
    print(f'{reduction:.1%} less time')

if __name__ == '__main__':
    run_comparison()
# Manual nesting 2.41s
# Composed nesting 2.29s
# 5.0% less time
posted @ 2021-07-04 22:34  MrSu  阅读(68)  评论(0编辑  收藏  举报