Python知识点大杂烩

Posted by iceyao on Thursday, March 21, 2024

Python知识点

什么是yield

yield的函数被称为生成器(generator)。跟普通函数不同的是,生成器是一个返回迭代器的函数,只能用于迭代操作。

def foo():
    print("开始...")
    while True:
        response = yield 1
        print("response = ", response)


def main():
    g = foo()
    print("第一次yield返回值:", next(g))
    print("第二次next")
    print("第二次yield返回值:", next(g))

输出:

开始...
第一次yield返回值: 1
第二次next
response =  None
第二次yield返回值: 1
  1. foo函数中包含yield关键字,所以foo函数不会真正执行,而是得到一个生成器g
  2. 对生成g调用next方法时,foo函数才会真正执行,先执行foo函数中的print(“开始…"),然后进入while循环
  3. 执行遇到yield关键字,先把yield看成是return,return 1之后,程序停止。并没有完成赋值给response的操作,到这里next(g)执行完成
  4. 执行print(“第二次next”)
  5. 执行新的next(g),执行的位置要从上一个next方法停止的位置开始,即要完成赋值操作给response,因为上一个next已经return了,所以 右边相当于没有值,输出就是response = None
  6. 还没遇到第二次的yield,所以程序还未停止,进入while循环,遇到yield 1,返回打印1

使用send的例子


def foo():
    print("开始...")
    while True:
        response = yield 1
        print("response = ", response)


def main():
    g = foo()
    print("第一次yield返回值:", next(g))
    print("第二次next")
    print("第二次yield返回值:", g.send(2))

输出:

开始...
第一次yield返回值: 1
第二次next
response =  2
第二次yield返回值: 1

前面执行过程跟上面那个例子一样,从g.send(2)开始,程序会从上一个next()停止的下一步操作开始,send的话是会把2赋值给response变量。 其实next()和send()在一定意义上作用是相似的,区别是send()可以传递yield表达式的值进去,而next()不能传递特定的值,只能传递None进去。因此next(c)可以等价于c.send(None)。

生产者-消费者

yield/send实现

def consumer():
    r = ''
    while True:
        n = yield r
        if not n:
            return
        print('消费者: 消费 %s...' % n)
        r = '200 OK'


def producer(c):
    c.send(None)
    n = 0
    while n < 5:
        n = n + 1
        print('生产者: 生产 %s...' % n)
        r = c.send(n)
        print('生产者: 从消费里那里返回: %s' % r)
    c.close()


def main():
    c = consumer()
    producer(c)


if __name__ == "__main__":
    main()

  1. consumer函数定义了一个生成器,它能够消费由生产者发送的值。
  2. 在consumer函数中,while True 创建了一个无限循环,在这个循环中,生成器始终等待来自生产者的值。
  3. n = yield r 这一行既是yield表达式的输出,也是输入的接收点。该生成器一开始会返回空字符串 r='',之后每次循环会返回 ‘200 OK’ 给生产者,并且等待下一个来自生产者的send。
  4. 如果生产者发送了None或没有发送值(即 n 为假),消费者将会直接返回,生成器结束。

生产者的producer函数做了以下几件事情:

  1. 使用c.send(None) 启动或"预激活” 消费者生成器,这是开始发送值给生成器之前的必要步骤。
  2. produce 开始在一个循环中生产值,从1到5。
  3. 对于每个新的整数 n,生产者通过调用 c.send(n) 将其发送到消费者,同时接收从消费者返回的值,并将其打印出来。
  4. 生产者在发送了5个值后结束循环,并通过 c.close() 关闭消费者生成器。

main函数会先创建消费者生成器,然后启动生产者函数。最终运行脚本时,会依次打印出生产者生产的值和消费者消费的值,以及消费者返回的结果(‘200 OK’)。运行过程中,生产者和消费者通过send和yield操作进行交互,协同工作完成生产消费任务。

输出:

生产者: 生产 1...
消费者: 消费 1...
生产者: 从消费里那里返回: 200 OK
生产者: 生产 2...
消费者: 消费 2...
生产者: 从消费里那里返回: 200 OK
生产者: 生产 3...
消费者: 消费 3...
生产者: 从消费里那里返回: 200 OK
生产者: 生产 4...
消费者: 消费 4...
生产者: 从消费里那里返回: 200 OK
生产者: 生产 5...
消费者: 消费 5...
生产者: 从消费里那里返回: 200 OK

async/await协程实现

import asyncio
import random
import time


async def consumer(queue, id):
    while True:
        val = await queue.get()
        if val is None:  # 如果接收到None,则认为是结束信号
            # 通知队列任务完成
            queue.task_done()
            break
        print('{} get a val: {}'.format(id, val))
        # 模拟一秒钟处理时间
        await asyncio.sleep(1)
        # 通知队列任务完成
        queue.task_done()


async def producer(queue, id):
    for _ in range(5):
        val = random.randint(1, 10)
        await queue.put(val)
        print('{} put a val: {}'.format(id, val))
        await asyncio.sleep(1)


async def main():
    queue = asyncio.Queue()

    consumers = [asyncio.create_task(
        consumer(queue, f'consumer_{i+1}')) for i in range(2)]
    producers = [asyncio.create_task(
        producer(queue, f'producer_{i+1}')) for i in range(2)]

    # 等待所有生产者结束
    await asyncio.gather(*producers)

    # 发送结束信号给消费者,有几个消费者就发送几个None
    for _ in consumers:
        await queue.put(None)

    # 等待所有任务被消费
    await queue.join()  # 这确保了队列中的所有任务被处理

    # 取消所有消费者
    for c in consumers:
        c.cancel()

    # 等待所有消费者完成取消
    await asyncio.gather(*consumers, return_exceptions=True)


if __name__ == '__main__':
    start_time = time.time()
    asyncio.run(main())
    print('time cost:', time.time() - start_time)

输出:

producer_1 put a val: 7
producer_2 put a val: 5
consumer_1 get a val: 7
consumer_2 get a val: 5
producer_1 put a val: 9
producer_2 put a val: 2
consumer_1 get a val: 9
consumer_2 get a val: 2
producer_1 put a val: 9
producer_2 put a val: 3
consumer_1 get a val: 9
consumer_2 get a val: 3
producer_1 put a val: 5
producer_2 put a val: 1
consumer_1 get a val: 5
consumer_2 get a val: 1
producer_1 put a val: 6
producer_2 put a val: 7
consumer_1 get a val: 6
consumer_2 get a val: 7
time cost: 5.012088060379028

隐式new方法和init方法

  • __new__()方法用来创建实例,它是class的方法,是个静态方法,执行完了需要返回创建的类的实例。
  • __init__()方法用来初始化实例,在实例对象被创建后被调用,是实例对象的方法,通常用于设置实例对象的初始属性。__init__()方法将不返回任何信息。
  • 类中同时出现了__init__()方法和__new__()方法,调用顺序为:先调用__new__()方法,后调用__init__()方法。__new__()方法如果报错,则不会调用__init__()方法。

重写__new__方法

def __new__(cls):
      return super().__new__(cls) # 或return object.__new__(cls)

隐式new方法应用 - 单例模式

class TestCls:
    _xxx = None
    def __new__(cls):
        print('__new__ func called')
        if cls._xxx == None:
            cls._xxx = object.__new__(cls)
            return cls._xxx
        else:
            return cls._xxx
    def __init__(self):
        print('__init__ func called')


t1 = TestCls()
print(t1)
t2 = TestCls()
print(t2)

输出:

__new__ func called
__init__ func called
<__main__.TestCls object at 0x7f8fe75befe0>
__new__ func called
__init__ func called
<__main__.TestCls object at 0x7f8fe75befe0>

可以看出是同个对象,而且隐式new函数先于隐式init函数被调用

functools.wraps

保证被装饰器装饰后的函数还拥有原来的属性,wraps通过partial以及update_wrapper来实现。比如保留原有函数名和doc

没用wraps的装饰器

def decorate(func):
    def wrapper():
        return '<decorate>' + func() + '</decorate>'
    return wrapper


@decorate
def to_decorate():
    return 'to decorate!'


def not_to_decorate():
    return 'not to decorate'


print(to_decorate)
print(not_to_decorate)

输出:

<function decorate.<locals>.wrapper at 0x7fa1e6ddec20>
<function not_to_decorate at 0x7fa1e6ddfd90>

从输出来看,装饰后的函数名发生了变化

用了wraps的装饰器

import functools


def decorate(func):
    @functools.wraps(func)
    def wrapper():
        return '<decorate>' + func() + '</decorate>'
    return wrapper


@decorate
def to_decorate():
    return 'to decorate!'


def not_to_decorate():
    return 'not to decorate'


print(to_decorate)
print(not_to_decorate)

输出:

<function to_decorate at 0x7fd9bfdd2c20>
<function not_to_decorate at 0x7fd9bfdd3d90>

从输出来看,装饰后的函数跟原来的函数保持了一致。知乎上有篇详解functools.wraps原理的文章:https://zhuanlan.zhihu.com/p/45535784

「真诚赞赏,手留余香」

爱折腾的工程师

真诚赞赏,手留余香

使用微信扫描二维码完成支付