Celery任务中any()语句返回错误结果的问题排查
这个问题的核心是你误用了Python的对象身份运算符is not来比较字符串值,而非值比较运算符!=,在Celery分布式模式(非always eager)下,这个差异会导致完全不符合预期的结果。
为什么会出现这种矛盾?
Eager模式下的巧合
当CELERY_ALWAYS_EAGER = True时,所有任务都在当前进程内执行,Python的字符串驻留(intern)机制会把短字符串(比如'SUCCESS')缓存起来,所以x.status和你写的'SUCCESS'是同一个内存对象,此时x.status is 'SUCCESS'会返回True,你的过滤条件if x.status is not 'SUCCESS'能正确过滤掉成功的任务,最终得到空列表,any()对空列表返回False,符合预期。非Eager模式下的问题
关闭eager模式后,Celery的worker在独立进程(甚至远程机器)中运行,任务执行完成后返回的status字符串是一个全新的对象,和主进程中你写的'SUCCESS'不是同一个内存实例。此时x.status is not 'SUCCESS'会返回True——哪怕字符串内容完全一样!这就导致你的列表推导式
[x.status for x in self.resdict['jobs'].values() if x.status is not 'SUCCESS']实际上把所有任务的status都包含了进来(因为每个都满足is not 'SUCCESS'的条件),而这些status都是'SUCCESS'字符串。在Python中,非空字符串在布尔判断中被视为True,所以any()检查这个全是'SUCCESS'的列表时,自然返回True。
修复方案
把所有用于比较字符串值的is/is not替换成==/!=,这才是正确的字符串值比较方式。修改后的代码如下:
# 修复while循环的判断条件 while any([x.status != 'SUCCESS' for x in self.resdict['jobs'].values()]): print([x.status for x in self.resdict['jobs'].values() if x.status != 'SUCCESS']) print(any([x.status != 'SUCCESS' for x in self.resdict['jobs'].values()])) time.sleep(.5) else: for a in self.resdict['accounts'][0]['result']: account = a['number'] if isinstance(a['number'], str) else False if account: # 增加判断避免False值的干扰 self.resdict[account] = self.resdict['jobs'][account].result[0]['result'][0]['Device Information']['subattributes']
额外优化建议
你可以简化while循环里的逻辑,避免重复遍历计算列表:
# 提取任务状态到变量,避免重复遍历 job_statuses = [x.status for x in self.resdict['jobs'].values()] while any(status != 'SUCCESS' for status in job_statuses): pending_statuses = [status for status in job_statuses if status != 'SUCCESS'] print(pending_statuses) print(len(pending_statuses) > 0) # 等价于any(...),更直观 time.sleep(.5) # 重新获取最新状态 job_statuses = [x.status for x in self.resdict['jobs'].values()]
这样不仅更高效,代码可读性也更强。
内容的提问来源于stack exchange,提问作者user1601716

