Python技术迷

外包偷吃了正编的下午茶,被公司定为恶性事件~

刚看到个新闻,说深圳一家公司发通告,外包偷吃了正编的下午茶,居然被定性成“恶性事件”,后续还搞出隔离区+赔偿的处理措施🤯。

Image

作为程序员,我第一反应是:这事儿虽然小,但暴露出公司内部对“正编 vs 外包”的身份鸿沟。网友有人笑说为了一点零食上纲上线,但我觉得关键点在于 信任感。程序员写代码时最怕的就是“二等公民”心态,如果公司在茶水间就画起界限,那工作协作时矛盾只会更大。

Image

换个角度看,外包吃个点心,本不至于惊动通告,但公司借题发挥,其实是在释放一种信号:你们不是一家人。对团队氛围来说,这才是真正的恶性事件。技术活儿讲究配合,就像分布式系统一样,节点间要互信,如果天天设防,效率必然下降。

总的来说,这事提醒我们:制度可以严,但边界要合理。【备注:文末可领最新资料】

面试题:Promise 对象池

昨天晚上十一点多,在公司楼下吹风,我手机里还在排一大堆「抓数据」的小任务,网速一好就全冲上去了,服务器直接喘不过气。就这个…我才想起「Promise 对象池」这种老办法:别一股脑儿全跑,按固定并发数,来了排队,跑完再放下一个。虽然名字听着像前端那套 Promise,但咱用 Python 也能玩,同样的思路,换成 asyncio 就行。

对象池是个啥,为什么要它

你想象一下,有 500 个异步任务,每个都要打外网请求。要是全开,带宽、连接数、数据库、甚至对方接口…都可能被你自己「挤爆」。对象池干的事很朴素:限制同时在跑的任务数量,比如只允许 10 个在跑,剩下的老老实实等。跑完一个,下一位补上,就像电梯超载会等人出来再进去,是不是这个意思。

用 Python 写一个「Promise Pool」的小心机

我们用 asyncio.Semaphore 来做并发闸门。任务列表里放「可调用,返回协程」的函数(注意是函数不是直接协程,这样启动时机我们可控),结果还要按原来的顺序放回去,出了错也别无声失败,给你个异常对象自己看着办。代码我就贴这一个,够用了:

import asyncio
from typing import Callable, Awaitable, Any, List

classPromisePool:
def__init__(self, limit: int):
if limit <= 0:
raise ValueError("limit 必须 > 0")
        self._sem = asyncio.Semaphore(limit)

asyncdefrun(self, coro_factories: List[Callable[[], Awaitable[Any]]]) -> List[Any]:
        results: List[Any] = [None] * len(coro_factories)

asyncdef_runner(idx: int, make_coro: Callable[[], Awaitable[Any]]):
asyncwith self._sem:
try:
                    results[idx] = await make_coro()
except Exception as e:
# 也可以在这里重试/记录日志,这里先把异常塞回去
                    results[idx] = e

        tasks = [asyncio.create_task(_runner(i, f)) for i, f in enumerate(coro_factories)]
await asyncio.gather(*tasks)
return results

# 演示:假装去“请求网络”,其实就是 sleep
asyncdeffake_fetch(name: str, delay: float) -> str:
await asyncio.sleep(delay)
returnf"{name} done in {delay:.1f}s"

asyncdefmain():
# 把协程“工厂”塞进去,而不是直接把协程对象塞进去
    jobs = [
lambda n=n, d=d: fake_fetch(n, d)
for n, d in [("A", 1.2), ("B", 0.3), ("C", 0.8), ("D", 0.5), ("E", 1.0)]
    ]
    pool = PromisePool(limit=2)
    results = await pool.run(jobs)
for i, r in enumerate(results):
if isinstance(r, Exception):
            print(f"任务{i}挂了:{r}")
else:
            print(f"任务{i}结果:{r}")

if __name__ == "__main__":
    asyncio.run(main())

你看,关键点就仨:一是 Semaphore(limit) 把出口卡住,二是「工厂函数」让我们在真正拿到锁的时候才创建/启动协程,三是结果按索引回填,顺序可控。异常不吞,直接放回列表,外面想重试就再包一层策略。

我昨天踩的就两个。第一,很多人把协程对象提前建好了再丢进去,结果并没有限住——因为协程已经开始等了…对吧,所以上面用的是「返回协程的函数」。第二,别把 gather 的 return_exceptions=True 乱开,和我们自己收集异常会打架,除非你真想两头混着处理。哦对,还有个小优化,任务特别多的时候可以分批生成工厂,别一次性构造上万闭包,内存也会顶着难受。

什么时候加重试、超时、取消

场景要是偏「抓数据」这种抖一抖就好的,给每个工厂再包一层重试就行,像指数退避那套;要是接口会卡死,就在 _runner 里把 await 外面再套个 asyncio.wait_for,来个硬超时;取消的话,你在上层收到 Ctrl+C,遍历 tasks 调 cancel(),池子会跟着收摊。这些都不影响核心思路:并发有上限,排队有秩序。

-END-

我为大家打造了一份RPA教程,完全免费:songshuhezi.com/rpa.html

🔥虎哥私藏精品🔥

虎哥作为一名老码农,整理了全网最全《python高级架构师资料合集》,总量高达650GB,点击下方公众号回复关键字 python 全部免费领