运算符重载与过度封装的弊病——记一次python debug

注:标题用了C++的说法,实际上python里应该叫运算符对应的魔法方法?不过大致意思一样,而且标题太长估计不好看。

久仰pandas包的大名,今天要处理csv文件,便去翻了翻文档,看了下入门,然后开始写代码。

需求十分简单:从数据里筛出来'total'字段大于500,并且id在某个集合内的所有数据。

由于上述需求基本上需要两个过滤器(Mask,非filter)来描述,因此我写出了如下代码:

doctors_500 = doctors_300[lamdba rec: rec['total'] > 500][lambda rec: rec['id'] in id_allow]

其中doctors_300 是一个 DataFrame

运行之后,自然python报错。细究原因,发现是in运算符输出的是一个布尔值,而非一个Series类的实例;心里大概有了数,随便一搜,果然in只对应一个__contains__魔法方法。这里我们的Series类在 in关键字的左边,没法对in做什么手脚。

再去翻Series类的文档,里面果然有我们想要的isin方法。不过在这过程中由于觉得自己的代码太丑顺便去翻了一下StackOverflow,发现这种过滤器的连锁(chain)一般是先对过滤器进行与操作,最后在进行过滤。

于是改写代码如下:

doctors_500 = doctors_300[doctors_300['total'] > 500 and doctors_300['id'].isin(id_allow)]

运行之后,自然python报错。细究原因,发现是and不吃__and__重载;这里应该使用&运算符。

于是改写代码如下:

doctors_500 = doctors_300[doctors_300['total'] > 500 & doctors_300['id'].isin(id_allow)]

运行之后,解释器倒是没报错;但输出的结果完全不对。这两个表的内容竟然一模一样!

单独打印两个过滤器,其值均正确;但是把两个过滤器一and起来,其结果竟然是一个元素均为TrueSeries.这令我大呼吃惊,一度质疑是&运算符对应的__and__魔法方法出了问题。

当了半天人工解释器(VSCode的Go to Definition时灵时不灵,写代码时是疯狂星期四,不灵),从core.arraylike里找到了这个魔法方法的定义(略),发现是委托给子类的_logical_method方法进行运算;又翻回去看Series类的该方法(同略,大致就是对自己的_values列表与输入的参数进行对应运算),结果发现好像没什么问题。

思来想去,折磨良久,最后在不断的print debug中习惯性的给>两边加上了括号,加完之后突然意识到:难道是优先级问题?。

于是改写代码如下:

doctors_500 = doctors_300[(doctors_300['total'] > 500) & doctors_300['id'].isin(id_allow)]

遂运行成功,结果正确。

写完之后也在想:虽然pandas的这种对魔法方法的“滥用”确实是使得不少代码写起来非常简便,但是也使得本来是对代码执行过程进行描述的语言看起来越来越像一种声明性语言。这里的&虽然广义上确实算是按位与,但其意义与该运算符原本的意义差了十万八千里,并且在写代码时我脑中所想的确实就是“这里应该要用一个逻辑与”。虽然这种问题出过一次后就不会再出第二次(而且&在python里大概除了拿去重载和写加密之类的脚本之外很少自己使用),但是在最开始接触pandas框架时,“完全不知道这玩意是如何实现的”这种虚浮感到即使现在仍然让我无法忘怀。简便归简便,但是这一类框架显然很难做到完全的开箱即用;而一旦在使用中出了什么问题或者有了什么教程外的额外需求,往日的简便很容易就会变成debug的泥潭。(我相信,如果换成我的物理系同学,会被折磨很久很久)

之前在上手Vue框架的时候,我也曾经遇到过同样的问题;即使我已经能写得一手相对漂亮的代码,但是我对Vue背后的响应式原理一无所知;一旦响应式出了问题,我就会像无头苍蝇一般乱撞。不过幸运的是,Vue.js提供了大量详尽的进阶文档,让我可以找到一些问题的答案,并且了解这背后的原理。而就算是今天用的Pandas框架,在User Guide下的最后一节FAQ中也提到了我所遇到的问题背后的逻辑(虽然我写到这特意去搜才发现)。

总而言之,这一类对原语言进行大量改造来方便用户在原语言的基础上用指定的语法快速方便达成某项功能的框架,虽然对于新手来说确实开箱易用,但也会使得用户很难真正掌握这一框架:因为它的很多行为都不符合一般的预期。我认为,框架在面对这一类问题上还是应当进行适当的取舍:在提供一部分开箱即用的同时,保留一些“马脚”,露出一点底层结构,来时时刻刻提醒用户不要把这些语法糖当真。(e.g.我个人觉得Vue.js的Ref类使用时不需要.value这一实验性语法糖最好不要启用)此外,这些框架应当为自己的设计给出详细的文档,来方便用户的学习与查错。

虽然以上行为都可以归结为一件问题:我确实忘了python里&的优先级高于>......

(大概是被C编译器开了-Wall后的Warning惯坏了)

posted @ 2022-10-07 01:14  tadshi  阅读(40)  评论(0编辑  收藏  举报