分类: 日记

  • Axios和FastApi的一些坑

    刚开始接触 FastApi 想做个小项目玩玩,前端用的React在用axios发送post请求时出现422错误??,422错误是指发送的post请求的字段和后台的接收字段不对应导致的

    @router.post('/paper/like')
    def like(pid: int):
        res = paper.like(pid)
        if res >= 1:
            return {'message': 'success'}
        else:
            return {'message': 'failed'}

    这是会报错的后端代码,看上去很正常对吧,就是一个字段pid呀,摸索了半天才发现原来是FastApi的验证所导致的,使用Pydantic模型后可以正常接收请求!

    class LikePaper(BaseModel):
        pid: int
    
    @router.post('/paper/like')
    def like(item: LikePaper):
        res = paper.like(item)
        if res >= 1:
            return {'message': 'success'}
        else:
            return {'message': 'failed'}

    这样使用Pydantic模型修改代码后就可以正常运行啦~

    axios发送请求失败
    fastapi自带的docs可以发送

  • Python 的 async

    我第一次接触 异步编程 肯定是在JavaScript,第一次写延时函数的时候很奇怪为啥延时函数下面的代码会直接执行而不等待时间结束,然后就对异步编程有了一点点的了解,后面在看一个Python的qq机器人项目的时候发现里面使用了大量的异步函数,可惜没有去认真学,今天就来重新 补补 python 的异步实现~

    什么是 异步 ? 什么是 同步?

    举一个简单的例子,假设有一个爬虫程序,需要爬取一百张图片,同步的方法就是从第一张开始爬取,先发送请求,然后下载,保存,然后循环继续请求第二张,下载保存······ 异步的方式呢,就是遇到需要消耗大量时间的IO时会先去执行其他的函数,我们的下载保存就是整个爬虫程序中最需要时间的部分,可以通过异步编程,在发送第一个图片下载请求后等待下载图片的时间不闲着继续发送第二个请求,然后一直发送,发送请求的速度可以忽略,相当于100张图片在同时下载,不考虑自身网络情况,假设一张图片的下载需要1s,那同步的实现方式需要100s才能爬取全部图片,而异步只需要1s多一点,效率翻 N 倍!

    怎样实现异步编程

    从 Python 3.5 开始引入了 async 和 await 关键词:

    import asyncio
    
    
    async def a():
        print('a')
        await asyncio.sleep(2)
        print('a')
    
    
    async def b():
        print('b')
        print('b')
    
    
    asyncio.run(asyncio.wait([a(), b()]))
    

    如上述代码块,在函数前声明async就表示当前函数为协程函数,函数内可以使用await 用于等待高 IO 代码的执行,其中的a函数在 输出第一行a之后会休眠2s,这时候不会去一直等他,而是会先去执行b函数,所以最终的输出结果为:

    a
    b
    b
    a

    最后一行 asyncio.run(asyncio.wait([a(), b()])) 中的 asyncio.run() 这个API是Python 3.7 引入的,用来简便的去执行异步函数~

    然后来详细的说说 python 的异步编程的原理

    1.await

    await 后面需要加可等待的对象,例如协程函数,Future,Task对象,IO 等待

    import asyncio
    
    
    async def main():
        print('1')
        await asyncio.sleep(2)
        print('2')
    
    
    asyncio.run(main())
    

    例如 上面的 await 后面跟的IO等待,这个时候如果有其他的协程函数在任务列表中,会先去执行其他函数,等待 await 后面的对象执行完成后 才会继续往下输出 2所以await简单来说就是一个等待标志,执行到这里的时候就可以先去执行别的函数了,等待执行完成后再继续往下执行,这也是异步的核心内容了。

    2.Task 对象

    可以在事件循环中并发的添加多个任务,也就是可以把多个协程函数当成任务放到一个任务列表中,在Python 3.7 以上版本中可以使用 asyncio.create_task API创建任务对象,如上面await中所说的,任务对象可以直接放在await关键词后面~

    import asyncio
    
    
    async def test_case():
        print(1)
        await asyncio.sleep(1)
        print(2)
    
    
    async def main():
        task1 = asyncio.create_task(test_case())
        task2 = asyncio.create_task(test_case())
        await task1, task2
    
    
    asyncio.run(main())
    

    上面代码的输出结果为:

    1
    1
    2
    2

    上面是使用了task1和task2来储存任务对象,我们可以使用函数列表来简化一下main函数的代码:

    async def main():
        task_list = [asyncio.create_task(test_case()) for i in range(2)]
        await asyncio.wait(task_list)

    直接把任务放在一个列表中,然后使用await去等待执行,但是列表对象显然不是之前说的await后面可放的内容,所以我们要使用asyncio.wait API来把任务列表转化成任务对象。

    3.Future 对象

    这是任务类的基类,任务对象就是在Future基础上实现的,Task内部的await处理时基于Future对象来的。

    例如:

    import asyncio
    
    
    async def main():
        loop = asyncio.get_running_loop()
        future = loop.create_future()
        await future
    
    
    asyncio.run(main())

    首先通过 loop 创建一个当前事件循环,然后创一个空的future对象,await这个对象,这样会一直在等待获取futur的结果,显然我们没有让future处理返回结果,所以会一直等待下去。再看 下面的代码:

    import asyncio
    
    
    async def set_result(fut):
        await asyncio.sleep(1)
        fut.set_result('结果')
    
    
    async def main():
        # 获取当前时间循环
        loop = asyncio.get_running_loop()
        # 创建 future 对象,没绑定任何行为就不会结束此任务
        future = loop.create_future()
        # 创建 task 对象,绑定了 set_result 函数
        # 此函数 1s 后会给future赋值结果,future就可以正常结束了
        await loop.create_task(set_result(future))
        res = await future
        print(res)
    
    
    asyncio.run(main())

    这时候,会在1s后正常输出个“结果”然后结束程序的运行。当然本段代码没有什么实际的意义,就是说明future和task的区别,我们很少会手动去用future,一般直接使用task即可。

    4.concurrent.futures.Future对象

    这个对象和上文的Future对象没有任何关系,如果你想使用线程池或者进程池来实现异步操作时用到的对象。比如可以通过它实现python异步模块和同步模块的混用。

    例如通过使用线程池来实现异步:

    import time
    from concurrent.futures.thread import ThreadPoolExecutor
    
    
    def f(val):
        time.sleep(1)
        print(val)
    
    
    # 创建线程池
    pool = ThreadPoolExecutor(max_workers=5)
    
    for i in range(10):
        pool.submit(f, i)
    

    输出的结果为:

    021
    
    4
    3
    
    75
    8
    9
    6

    可以看到数字完全乱了,这是因为多线程同时输出导致的,同样这样我们也实现了异步编程,而且用到的time库而不是asyncio.time,通过concurrent.futures.Future的线程池或者进程池我们可以把一些同步模块和一些异步模块混合使用。

    5.实现 asyncio 和不支持异步的模块结合使用

    比如一个爬虫案例:

    async def download(url):
        loop = asyncio.get_event_loop()
        future = loop.run_in_executor(None, requests.get, url)
        res = await future
        return res.content

    这样我们把requests.get放到线池中去,就可以实现异步爬虫啦

  • 喵?

    做核酸的时候遇到的,好乖好可爱

  • 第十三届WC战神杯?

    本来是比较认真对待这次比赛的,结果突然说要搞线上赛,搞就搞叭,所谓的蓝桥杯先进的反作弊系统就是分享屏幕和摄像头拍摄?

    还允许中途掉线,允许上厕所···

    以后没必要再参加这个比赛了

  • 此情可待成追忆

    云烟成雨
    呜呜呜
    云烟成雨
    不知道咋的突然听这首就很有感觉
    狗蛋
    你想到啥了
    云烟成雨
    上学期
    云烟成雨
    因为这首歌也是上学期出的嘛
    狗蛋
    不知道你们有没有这种感觉,循环播放某一首歌的时候,顺便把那段时间的时光也记住了,然后过了许久再次播放那首歌的时候,又会想到那个时候的时光, 真的真的就这个样子的。
    云烟成雨
    对!

    从二零一六年的五月份注册了网易云音乐之后一直在用它听歌,我还记得第一首喜欢的日文歌是《クリスマスの少年》然后就开始沉迷日文歌曲啦,找到好多好多好听的,而且似乎每一段时光都有对应的歌曲,今天随机播放的时候,听到漠河舞厅。脑子里突然出现上学期某个下午,在物理实验室,晚上,在礼堂旁边,一个人在旁边唱着这首歌······

    青山依旧在

    几度夕阳红

    临江仙·滚滚长江东逝水·杨慎

    照片也是记录生活的绝佳途径,但是我好像很少拍照记录呀

    狗蛋
    我也好无聊
    狗蛋
    刚刚想写报告来着
    狗蛋
    又翻开手机相册了
    云烟成雨
    呜呜真好
    云烟成雨
    我手机相册都没东西
    狗蛋
    那你有多少照片啊
    云烟成雨
    之前有三百多吧,上次把一些删了以后就剩下四五张了
    狗蛋
    好家伙,一天都不止四五张,看见好看的夕阳,有趣的东西,都没有拍的欲望吗
    云烟成雨
    没有

    我又从之前的聊天中扒拉出前几天去散步时拍的照片:

    这拍照技术,呜呜呜,勿怪

    一个人在大树下弹着吉他唱着歌,很有意境的叭,不过当时没有好好拍,随手点了下,画面都有些晃动了