尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

Python not运算符详解:真值表、优先级与实战避坑指南

Python not运算符详解:真值表、优先级与实战避坑指南 1. not到底在干嘛从真值表到第一次实战1.1 一句话讲透not的逻辑本质搞Python基础的朋友迟早都会碰到逻辑运算符这一关而三个逻辑运算符里and与、or或大家可能还好理解一点not非看起来最简单真用起来却总有那么点“反直觉”。按我一开始学Python时的体会not就像电路里的“反向开关”——你给它什么值它就把这个值反过来给你你给真它返回假你给假它返回真。和and、or不太一样的地方在于not是单目运算符意思就是它只需要一个操作数不像and和or那样必须左右两边各放一个表达式。比如print(not True) # False print(not False) # True print(not 0) # True print(not 1) # False这个逻辑本身不难难的是很多人没有真正意识到not并不要求操作数必须是布尔值。在Python里任何对象都可以被当作布尔值来“求值”然后not再去反转它。这就引出了Python真假值判断的一套隐性规则只要把这个规则吃透not就算真正学会了。1.2 Python眼里的“真”与“假”真值表与隐式bool我在教朋友入门的时候经常让他们先背一张表在Python中以下值会被判断为“假”FalseFalse本身None数值0包括0、0.0、0j空序列和空集合包括空字符串、[]空列表、()空元组、{}空字典、set()空集合自定义对象中定义了__bool__()或__len__()方法且返回值为假除了这些其余所有东西都是“真”True。这张表其实就是Python的隐式布尔转换规则表not只是在这个基础上做一次取反。有时候为了确认一个对象的真假值我们也可以主动调用bool()来看结果print(bool(0)) # False print(bool()) # False print(bool([])) # False print(bool(abc)) # True print(bool([1])) # True print(bool(None)) # False那not在这里面起什么作用它就是把这个真假结果反过来。比如判断一个列表是不是空最直觉的写法是len(list) 0但Python里更地道的写法其实是not list——因为空列表本身就是假值取反就是真。这个写法的好处我后面会专门讲我先在这里留一个印象。1.3 最常见的三大误用场景光看概念容易飘我列几个新手最容易踩坑的场景都是我自己或者带过的同事真真实实碰到过的误用一把not用在错误的数据类型上。比如not False返回的是False因为字符串False是非空的字符串而不是布尔值False。很多人会被这个绕晕以为带“False”字样的字符串就是假值。print(not False) # 猜猜结果是多少 # 结果是 False因为 False 是非空字符串本身就是真值取反就是假误用二把not和比较运算符混在一起却不加括号。比如not 3 2的结果是什么如果不清楚优先级我会下意识觉得这是(not 3) 2但实际Python解析的是not (3 2)也就是not True结果是False。这就是优先级的问题我下一节专门展开。误用三需要判断“不等于”时去写not a b而不是a ! b。虽然这两种写法在当前场景下结果相同但可读性完全不一样。not a b会被解析成not (a b)别人读代码时还要先做一次脑筋急转弯而a ! b一目了然。not的优势场景是判断“整体逻辑的真假”而不是替代比较运算符。2. not的运算优先级最容易翻车的一个点2.1 优先级顺序与括号的用法Python中逻辑运算符的优先级是notandor而且它们的优先级都排在比较运算符之后。完整一点说算术运算符、-、*、/最高然后是比较运算符、、、、再然后才是not、and、or。我举个例子让你感受一下a 5 result not a 3 and a 10这段代码Python实际解析的顺序是先算a 3得到True再算a 10得到True然后算not True得到False最后算False and True得到False所以result是False。如果你本意是想表达“a不大于3且a小于10”那这个结果就不对了。正确写法必须加括号result (not (a 3)) and (a 10)说白了not的优先级比比较运算符低比and高夹在中间。一旦表达式里同时出现比较运算符和not千万不能想当然我建议一律用括号把逻辑分组写清楚。2.2 优先级翻车的真实案例and/or混搭有一个很经典的翻车现场我在实际代码评审里见过不止一次。有人写了一个判断“用户不是管理员并且账号没被禁用”的条件原意是is_admin为Falseis_disabled为False于是写成了if not is_admin and is_disabled False:单看这一行因为not is_admin先求值然后才轮到and所以逻辑是没问题的。但假设再加一个条件变成“用户不是管理员或者账号被禁用时都拒绝登录”if not is_admin or is_disabled:这行也没问题因为not优先级高于or它会先算not is_admin再算or is_disabled。但是如果你把and和or混在一起又不加括号比如if not is_admin and is_active or is_new_user:这一行实际解析成(not is_admin and is_active) or is_new_user而不是not (is_admin and (is_active or is_new_user))。逻辑就完全变了。我见过好多次这种条件加上去之后测试用例永远过不去的场景最后排查下来全在优先级上。2.3 优先级速查与安全写法我直接把经验总结成一张速查表方便你贴在手边参考运算符优先级高到低示例算术运算最高3 5 * 2比较运算次高a 3、b 1not中等not a 3and较低a and bor最低a or b安全写法其实就只有一条铁律只要混合了不同类型的运算符就给核心逻辑加括号。加括号不会降低性能也不会被同行嘲笑反而会让代码看起来严谨、可读性强。我自己的习惯是多个逻辑表达式组合时每一个独立语义都用一对括号包起来比如if (not is_admin) and (is_active or is_new_user):这样一看就懂完全不需要考虑优先级的问题连带着代码审查都轻松不少。3. 实战用not解决真实开发里的问题3.1 用not做空值兜底比len更简洁我在写Python的早期判断列表为空时用的是if len(my_list) 0:后来看项目里老手写的代码人家用的是if not my_list:第一次看到我还愣了一下后来才意识到两者的逻辑一模一样空列表是真值表中的假值not []就是True。这种写法不仅少打几个字符更重要的是语义上更贴近Python的风格——你关心的不是“长度是多少”而是“这个列表里有没有东西”。这个思路可以延伸到各种容器name input(请输入姓名) if not name.strip(): print(姓名不能为空) data get_data_from_api() if not data: print(没有获取到任何数据)not在空值兜底这个场景下可以说是最佳拍档。比如从接口拿数据返回的可能是一个空列表、空字典也可能是None用if not data一个判断就能覆盖所有“没有拿到有效数据”的情况不用写成一长串if data is None or len(data) 0代码干净利落。3.2 用not做条件筛选与数据清洗另一个常见的应用场景是条件筛选尤其是处理字符串的时候。热词里提到了“python筛选一样的”、“python画图横坐标太密集”这些其实都会用到字符串或容器的真假值判断。比如筛掉列表里的空字符串raw_list [apple, , banana, , cherry, None] clean_list [item for item in raw_list if item and item.strip()] print(clean_list) # 输出[apple, banana, cherry]这里if item and item.strip()就同时利用了真值表规则None和空字符串是假值会被过滤掉 虽然是真值但strip()之后是空字符串也是假值也会被过滤掉。如果不想要这种隐式写法也可以用if not item or not item.strip()的反向条件来理解两种角度都可以。再比如数据清洗时经常要剔除“值为0但其实是有效数据”的情况not在这里也有用武之地scores [85, 0, 92, 0, 78] # 只想统计非零分数 valid_scores [score for score in scores if score ! 0] # 或者反过来看哪些是缺考 missing [score for score in scores if not score]第二种写法if not score利用了“0是假值”的规则一眼就能看出“分数为0被当作缺失”语义非常清晰。3.3 用not写循环终止逻辑写循环逻辑时not也经常能派上用场。经典场景是用户输入验证比如一直等用户输入一个非空值才继续while True: user_input input(请输入设备编号) if not user_input.strip(): print(输入不能为空请重新输入) continue break这种写法比判断len(user_input.strip()) 0更自然。还有一个场景是从队列或迭代器里取数据取到空的时候停止jobs [任务A, 任务B, None, 任务C] for job in jobs: if not job: print(遇到空任务跳过) continue print(f正在处理{job})not在这里的作用是“空即停、空即跳”配合continue或break使用控制逻辑一下子就清晰了。3.4 配合类型转换、abs等基础函数做练习热词里有python类型转换、python abs函数我用一个综合的小练习把not和这些基础内容串起来。假设我们要写一个函数判断输入值是不是一个“有效且非零的正数”def is_valid_positive(value): try: num float(value) except (TypeError, ValueError): return False if not num: return False # 也可以用 abs 判断非零 if abs(num) 0: return False return num 0 print(is_valid_positive(12)) # True print(is_valid_positive(-3)) # False print(is_valid_positive(0)) # False print(is_valid_positive()) # False print(is_valid_positive(None)) # False这个函数里not num判断的是“值为0或无法转换为有效数值”的情况abs(num) 0则是另一个角度。两个条件其实功能重复了但作为练手正好能看清not和普通比较运算之间的等价关系。这种细节琢磨多了写出来的代码会越来越有Python味道。4. not的进阶玩法多重取反与成员判断4.1 not in成员判断的标准姿势not还有一个非常高频的用法就是和in组合成not in用来判断一个元素是否不在某个容器里。这个写法等同于not (x in container)但可读性要强得多。比如热词里提到的“python爬虫”爬虫拿到的往往是一堆URL要去重、要过滤黑名单这时not in就是最常用的工具blacklist {http://spam.example.com, http://fake.example.com} url http://news.example.com/article/123 if url not in blacklist: print(可以抓取, url) else: print(命中黑名单跳过)再比如“python筛选一样的”经常需要从一份数据里找出不在另一个列表里的元素registered_users [alice, bob, carol] current_users [bob, dave, eve] new_users [user for user in current_users if user not in registered_users] print(new_users) # 输出[dave, eve]这里user not in registered_users就是一个标准的成员判断语义非常直白。它背后其实调用了容器对象的__contains__()方法列表是逐个比较集合和字典是哈希查找速度差别很大。所以如果数据量大记得把registered_users换成集合not in的性能会好很多。4.2 多重not与德摩根定律“非非就是是”not not等于一个正逻辑。这个特性看起来有点无聊但在某些代码里还真能派上用场。比如你想让一段代码的值严格是布尔类型而不是其他真值可以这样写value hello result not not value print(result) # True print(type(result)) # class boolnot not value的过程是not hello得到Falsenot False得到True最终把一个任意真值转换成了标准布尔类型。这个方法在有些场景比bool(value)看起来更有技巧感但可读性稍微差一点我个人更推荐直接写bool(value)除非你想显得自己很“Pythonic”。再往前一步函数式编程里经常用到德摩根定律not (A and B)等价于not A or not Bnot (A or B)等价于not A and not B。这个定律在优化条件表达式时非常好用。比如原本的条件是if not (is_vip and is_active): print(普通用户无法访问会员内容)用德摩根定律转换一下if (not is_vip) or (not is_active): print(普通用户无法访问会员内容)两种写法逻辑一模一样第二种在有些场景下更适合跟现有代码风格对齐。不过我不建议为了“炫技”而强行反转什么时候用关键是看哪一版让别人读起来更省力。4.3 把not用进列表推导式和爬虫实战前面提到了爬虫我把not放在一个更综合的场景里讲。假设你要写一个简单的爬虫任务目标是抓取一批文章页面的链接但需要跳过重复的和无效的链接raw_links [ http://example.com/a, http://example.com/b, , # 空链接 javascript:void(0), # 无效链接 http://example.com/a, # 重复链接 None ] def is_valid_link(link): if not link: return False if not link.startswith(http): return False return True seen set() unique_valid_links [] for link in raw_links: if not is_valid_link(link): print(f忽略无效链接{link}) continue if link in seen: print(f忽略重复链接{link}) continue seen.add(link) unique_valid_links.append(link) print(unique_valid_links) # 输出[http://example.com/a, http://example.com/b]这段代码里出现了三次not用法if not link过滤None和空字符串if not link.startswith(http)判断字符串是否以指定的前缀开头用not反向筛选if not is_valid_link(link)对函数返回的布尔值做整体取反startswith是一个字符串方法返回布尔值所以可以直接用not反向。类似的还有endswith、isdigit、isalpha、isalnum等一堆“以is开头的方法”它们返回的都是布尔值非常适合搭配not使用。比如def is_strong_password(password): if not password or len(password) 8: return False if not any(ch.isdigit() for ch in password): return False if not any(ch.isalpha() for ch in password): return False return True4.4 动态规划和算法题里的not热词里出现了“01背包动态规划python”、“动态规划最少硬币 python”很多人可能觉得算法题跟not关系不大。其实在处理边界条件时not也很常用。比如写动态规划转移方程时经常要对“空数组”“空字符串”这类边界做特判def coin_change(coins, amount): if not coins: return -1 if not amount: return 0 # 后续省略这里是动态规划主体逻辑这里not coins判断硬币列表为空not amount判断目标金额为0。两个判断都非常简短而且一眼就能读懂边界条件的意图。再比如处理字符串前缀、子序列的问题时if not s这种写法会频繁出现用来处理空串的边界情况。所以你会发现not看似是个不起眼的小运算符但它在“边界判断”这个维度上几乎是无可替代的存在。数据科学里的“python数据分析与可视化”、画图时处理横坐标密集、清洗空值本质上都是同一个套路先把无效数据识别出来然后跳过或过滤。not正是识别无效数据最常用的一把刀。5. 常见问题与排查技巧实录5.1 问题速查表我把我在答疑过程中遇到的高频问题整理成一个速查表每个问题都附了简短解释问题可能原因正确姿势not False结果是FalseFalse是非空字符串是真值要判断字符串内容先比较再取反如s ! Falsenot 3 2结果是False被解析成not (3 2)想清楚逻辑必要时加括号(not 3) 2没有意义实际是not 3是 Falseif not a b读起来很绕比较运算符优先级高于not直接用a ! b多重条件时结果总是出人意料not、and、or混用没加括号统一加括号分组把意图写明确not 0、not []返回 True 让人困惑0、空容器是假值记住真值表not not value看到了不敢确定双重取反等价于bool(value)可以放心用也可以改成bool(value)5.2 排查技巧用print调试not表达式遇到复杂的not表达式我基本不用那种“盯着代码脑子空转”的方式而是直接写个临时测试脚本把每一步的中间值打出来a 5 b 10 c 3 print(a b:, a b) # False print(not a b:, not a b) # True先比较后取反 print(a b and b c:, a b and b c) # False print(not (a b and b c):, not (a b and b c)) # True这样一打出来优先级到底怎么生效的一清二楚。我遇到过很多次困惑最后都是用这种方式解决的——不要让表达式在脑子里“跑”让它在终端里跑答案大都很直接。另外要注意一个细节在调试not和and、or混用的表达式时要特别留心“短路”行为。and和or都有短路求值特性也就是左侧表达式已经能决定结果时右侧表达式根本不会执行。比如items None if items and not items[0]: print(第一个元素是假值)这段代码不会报错因为items是None是假值and直接返回假not items[0]根本不会执行。如果你反过来写if not items[0] and items:那items[0]会先执行抛TypeError: NoneType object is not subscriptable。这个区别在实战中很致命尤其是处理可能为空的列表或字典时一定要把更“安全”的判断放在前面。5.3 独家避坑清单最后我整理一份自己踩过坑之后总结的清单算是这篇文章的“私藏干货”第一条不是说“假”就是False。在条件判断里0、、[]、{}、None都是假值但它们在身份上完全不是False。如果要做严格的类型判断记得用is False而不是not value。比如value 0 print(value False) # True因为 0 和 False 相等 print(value is False) # False因为 0 是 int 类型不是 bool 类型第二条not不能重载成“非黑即白”的语义。在业务代码里not is_active和is_active False绝大多数情况下等价但遇到is_active是None时前者是True后者是False。所以“未设置状态”的字段用not user.is_active判断时要特别小心它会把“未设置”和“明确不激活”混为一谈。我后来写这类代码时会更倾向于显式写user.is_active is False避免语义混淆。第三条尽量不要写not a not in b这种极端表达式。看起来像绕口令实际语法上也是合法的意思是not (a not in b)等价于a in b但这种写法除了增加阅读成本没有半点好处。我见过个别代码为了“炫技”写出这种行后来被整个团队要求统一改掉。第四条列表推导式里的if not ...用法有个隐藏细节。当你写[x for x in data if not x]选出的是所有假值元素也就是None、0、空字符串等。如果你只想选“为空的字符串”用这个写法会把0也选进来。正确做法是拿类型也判断一下data [None, , 0, hello, []] empty_strings [x for x in data if isinstance(x, str) and not x] print(empty_strings) # 输出[]5.4 几个适合练手的小练习如果你想把not练到条件反射我推荐几个小练习都不难但每一道都能让你对not的理解更扎实写一个函数is_blank(s)输入一个字符串如果是空字符串或者只包含空格返回True否则返回False。提示注意s本身可能为None一个列表里混着None、0、空字符串、False和正常字符串用一行列表推导式筛出所有“假值但不等于False”的元素。提示需要同时用not x和x is not False用not实现一个“密码强度检查”密码长度大于8位且不全是数字且不全是字母才算合格。这三个练习做下来你对not的隐式布尔转换、优先级、以及“同真假但不同身份”这几个概念基本就吃透了。最后再说一点我个人的习惯写在代码里的每一个not我都会在脑子里过一遍“如果这个变量是None、是0、是空字符串这条判断会怎么走”。把这三个“边界幽灵”想清楚了not用起来就不会翻车。逻辑运算符是整个编程语言里最基础但也最容易马虎的地方花点时间把它掰开揉碎后面学什么都会顺很多。
返回列表