把京东面试官电话当成送快递的了,让他帮我放门口。。
刚看到个贴子,一个网友说他接到京东面试官电话,还以为是送快递的,直接来一句“帮我放门口吧”,结果对面沉默了三秒才自报家门,太社死了哈哈。
网友们都笑翻了,也有人说“这说明现在面试压力太大,脑子都乱了”。
我觉得这事挺真实的。现在大家求职节奏太快,一天几个电话,确实容易分不清是谁。尴尬归尴尬,但其实也能看出这个人性格挺自然,不做作。反倒是面试官如果能幽默回应一句,气氛就轻松多了,说不定还加分。
总之,别太焦虑,面试嘛,有时出点小插曲,也能成为个好故事。【备注:文末可领最新资料】
面试题:设计可取消函数
那天晚上快十一点,我在公司楼下啃着个凉掉的汉堡,盯着电脑上一个“跑不完”的脚本发呆:查库、算报表、写文件,跑起来就是十几分钟,中途要停还得直接干掉进程,特别粗暴。那会儿我就在想,这玩意儿要是能像点外卖一样,“取消订单”多爽,所以就想着搞个“可取消函数”的设计出来。
简单说一句,所谓“可取消函数”,就是:函数在执行过程中,外面有人说一句“停”,它能比较优雅地自己收尾退出,而不是被系统一刀切掉。
先说下核心思路,别一上来就上代码。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 全部免费领