Python技术迷

把京东面试官电话当成送快递的了,让他帮我放门口。。

刚看到个贴子,一个网友说他接到京东面试官电话,还以为是送快递的,直接来一句“帮我放门口吧”,结果对面沉默了三秒才自报家门,太社死了哈哈。

Image

网友们都笑翻了,也有人说“这说明现在面试压力太大,脑子都乱了”。

我觉得这事挺真实的。现在大家求职节奏太快,一天几个电话,确实容易分不清是谁。尴尬归尴尬,但其实也能看出这个人性格挺自然,不做作。反倒是面试官如果能幽默回应一句,气氛就轻松多了,说不定还加分。

总之,别太焦虑,面试嘛,有时出点小插曲,也能成为个好故事。【备注:文末可领最新资料】

面试题:设计可取消函数

那天晚上快十一点,我在公司楼下啃着个凉掉的汉堡,盯着电脑上一个“跑不完”的脚本发呆:查库、算报表、写文件,跑起来就是十几分钟,中途要停还得直接干掉进程,特别粗暴。那会儿我就在想,这玩意儿要是能像点外卖一样,“取消订单”多爽,所以就想着搞个“可取消函数”的设计出来。

简单说一句,所谓“可取消函数”,就是:函数在执行过程中,外面有人说一句“停”,它能比较优雅地自己收尾退出,而不是被系统一刀切掉。

先说下核心思路,别一上来就上代码。Python 里普通函数一旦进入执行,解释器不会自动帮你打断,想中途停只有两条路:要么开线程/进程,把整个线程停掉;要么让函数自己“有点自觉”,隔一会儿问一句“有人让我停没”,有就提前返回。我们这篇用第二种,叫“协作式取消”,写起来也更贴近算法题那种感觉。

那怎么让“外面的人”和“里面的函数”打暗号呢?最常见的是搞一个“取消令牌”,可以简单粗暴理解成一个对象,上面只有两个操作:cancel 和 is_cancelled。

用 Python 写大概这样:

classCancelToken:
def__init__(self):
        self._cancelled = False

defcancel(self):
        self._cancelled = True

defis_cancelled(self):
return self._cancelled

这个类本身没啥魔法,就是个带状态的小旗子。真正关键是在耗时函数里,愿不愿意时不时抬头看一眼这面旗子。

比如来个假装很忙的任务:循环计算 + 偶尔 sleep 一下,顺手检查一下是不是该停了:

import time

deflong_task(n, token: CancelToken):
    total = 0
for i in range(n):
if token.is_cancelled():
            print("检测到取消请求,提前退出")
returnNone
# 模拟一点耗时操作
        total += i
if i % 1000000 == 0:
            time.sleep(0.01)
return total

外面用的时候就很像现实里催快递那个场景了,一个线程干活,另一个线程看不对劲就喊停:

import threading

token = CancelToken()

defworker():
    res = long_task(10_000_000_000, token)
    print("结果:", res)

t = threading.Thread(target=worker)
t.start()

# 假装过一会儿发现算太久了,不想等了
time.sleep(1)
token.cancel()

t.join()

这里有几个细节你可能会关心。

第一,为什么要循环里检查?因为取消是“软”的,不是那种直接杀线程,所以只能在一些“检查点”上判断要不要提前返回。循环体比较适合作检查点,IO 操作之前、每处理一批数据之后都可以看一眼 token。

第二,如果担心多线程里改这个状态有竞争,其实严格一点可以加锁,但很多场景下读多写少,只是从 False 变 True 一次,Python 自身的原子性已经够用了,除非你特别严格追求线程安全,可以这么写:

import threading

classSafeCancelToken:
def__init__(self):
        self._cancelled = False
        self._lock = threading.Lock()

defcancel(self):
with self._lock:
            self._cancelled = True

defis_cancelled(self):
with self._lock:
return self._cancelled

第三,如果你懒得每个函数都手动写一遍 if token.is_cancelled(),可以搞个小装饰器,让函数“天然”变成可取消的。思路是:原函数多要一个 token 参数,装饰器在调用前后自动帮你判断。

from functools import wraps

defcancellable(func):
    @wraps(func)
defwrapper(*args, token: CancelToken = None, **kwargs):
if token isnotNoneand token.is_cancelled():
            print("调用前就已经被取消了,直接不执行")
returnNone
        result = func(*args, token=token, **kwargs)
return result
return wrapper

配合刚才的 long_task 稍微改造一下:

@cancellable
deflong_task2(n, token: CancelToken):
    total = 0
for i in range(n):
if token and token.is_cancelled():
            print("函数内部检测到取消,退出")
returnNone
        total += i
return total

这样业务侧的写法就统一了:只要函数签名里愿意多要一个 token,你就随时能把它变成“可取消”的版本。项目里如果有一堆批处理、导入导出、批量推送之类的任务,全部挂在同一套 CancelToken 机制上,前端点一下“停止任务”,后端只要把相关 token 都 cancel 一次就行,既好控制,又不暴力。

说到底,这个“设计可取消函数”的题目,考察的其实就两块:一是你能不能抽象出一个简单的“取消协议”(这里就是 CancelToken);二是你能不能让函数在合适的地方配合这个协议做提前退出。Python 本身不会帮你抢占式打断,只能靠你自己写出这种协作式的逻辑。

等下次你写个脚本,发现自己又在那儿干等十几分钟,其实就可以顺手把这套可取消的思路加进去,体验会好不少。至于更狠的那种直接杀进程、杀线程,就留给运维同学和操作系统去头疼吧。

-END-

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

🔥虎哥私藏精品🔥

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