作者: ShiYu

  • 遇见纸船

    从茫茫大海飘散到各处的纸船承载着我们的期待和憧憬

    这个项目其实在高中的时候就在web和qq机器人平台上做过,类似漂流瓶的玩法,最近突然想为什么不利用微信的用户平台做一个小程序版本的小纸船~

    于是就做出来啦,界面大概是下面的样子(实机测试):

    做出来是做出来了,但是上架可谓是艰难曲折,第一次上架失败是因为小程序侵犯用户隐私,因为第一代我是直接读取了用户微信头像和昵称,并没有写自己的用户系统,直接在数据库储存了用户的信息。第二次我做了个独立的账号系统,结果来了下面的通知:

    ?玩个屁~

  • React 100%屏幕高度

    在一个新的项目中我想让我的APP组件高度占据整个屏幕,在App.css,index.css上面直接写height:100%不行,加上!important也不行,然后我直接在index.html里写内联样式,还是不行,最后发现可以通过vh实现百分百屏幕占比!

    .App{
      height: 100vh;
    }
  • 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放到线池中去,就可以实现异步爬虫啦

  • 喵?

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