一个由逗号引发的Bug——你可能不知道的元组创建方式
共 6339字,需浏览 13分钟
·
2020-11-02 10:03
「大家来找茬」,你知道问题所在吗?
import pandas as pd
from myproject.conf import settings
class MyDataObject:
def __init__(self, sql_result):
self.data = pd.DataFrame(sql_result)
def algorithm_handler(self):
if not self.data.empty:
try:
class_val = settings.CLASSVALUES #存放指定类别的列表
keywords = settings.KEYWORDS #文本描述中的关键词列表
df = (
self.data
.query(
"""
MY_CLASS.isin(@class_val) & \
DESC_CONTENT.str.contains(r'|'.join(@keywords))
"""
)
.groupby("grp")
.agg({"RECORD_ID": len})
.sort_values(ascending = False)
.reset_index()
.rename(columns = {"RECORD_ID": "FREQ"})
.assign(TYPE = "XX")
.loc[:, [...]]
)
return df
except ValueError:
log.exception(...)
import pandas as pd
from myproject.conf import settings
class MyDataObject:
def __init__(self, sql_result):
self.data = pd.DataFrame(sql_result)
def algorithm_handler(self):
if not self.data.empty:
try:
class_val = settings.CLASSVALUES, #存放指定类别的列表
keywords = settings.KEYWORDS #文本描述中的关键词列表
df = (
self.data
.query(
"""
MY_CLASS.isin(@class_val) & \
DESC_CONTENT.str.contains(r'|'.join(@keywords))
"""
)
.groupby("grp")
.agg({"RECORD_ID": len})
.sort_values(ascending = False)
.reset_index()
.rename(columns = {"RECORD_ID": "FREQ"})
.assign(TYPE = "XX")
.loc[:, [...]]
)
return df
except ValueError:
log.exception(...)
DataFrame.query()
时,由于表达式字符串不能够被 IDE 很好地解析,因此也不容易暴露出问题,这也就是在我以前这篇文章《高性能 Pandas 方法:query 和 eval》里所谈到的缺点。DataFrame
结果,所以在后期出错时,我几乎是从头到尾进行比对代码好几次都还没发现什么问题,就可能会和没看标题的你一样,丝毫没办法察觉当中有什么错误。class_val = settings.CLASSVALUES
这一行代码后面的「,」逗号。但是因为这个逗号则改变了我在配置文件中这么用于存放特定分类的列表的 class_val
变量的数据结构,加之表达式字符串的问题,所以无法能够第一时间发现问题所在。*.py
文件中以不起眼地姿态埋伏着,若不是靠着 Pycharm 的断点功能 Debug,仅凭借肉眼我们是几乎无法发现,找错找得几近绝望。Ugly, but effective.
这个逗号它导致了什么问题
In [1]: a = [1,2],
In [2]: a
Out[2]: ([1, 2],)
In [3]: b = [1,2]
In [4]: b
Out[4]: [1, 2]
a = [1,2],
后面多追加了一个逗号,最终的结果是变量 a
存储的是包含列表 [1,2]
的一个元组;而变量 b
则仍旧是一个列表。class_val
列表转换成了一个元组,因此表达式字符串里的表达式应该写成 MY_CLASS.isin(@class_val[0])
通过索引把列表取出来的形式才是正确;而 DataFrame.isin()
方法是将每个值和传入的比较对象的元素进行比对,但由于整个列表被包含进了元组里,所以实际比较的是元组中的元素,即整个列表,而不是列表中的单个元素。In [5]: a = 1, ; a
Out[5]: (1,)
In [6]: a = "1","2", ; a
Out[6]: ('1', '2')
In [7]: a = {"age":10}, ; a
Out[7]: ({'age': 10},)
In [8]: a = "10", [1,2,], ; a
Out[8]: ('10', [1, 2])
逗号在赋值表达式中的作用
mytuple = (1,2)
;另一种是通过关键字 tuple
关键字来转换数据类型并创建,如 mytuple = tuple([1,2])
。而这个逗号创建元组的方式其实可以算是第三种,但它本质上是等价于「打包」(Packing)。In [18]: def tuple_val():
...: return 1,2,3
...:
In [19]: type(tuple_val())
Out[19]: tuple
In [23]: a, b, c = tuple_val()
In [24]: print("{a}-{b}-{c}".format(a=a, b=b, c=c))
1-2-3
In [26]: a, = [1,]
In [27]: a
Out[27]: 1
In [29]: a, = [1,2]
---------------------------------------------------------------------------
ValueError Traceback (most recent call last)
-29-d160930f24aa> in
----> 1 a, = [1,2]
ValueError: too many values to unpack (expected 1)
ValueError
的报错信息已经告诉了我们缺少了变量用以接受解包(unpacking)的值。A tuple consists of a number of values separated by commas
ValueError: too many values to unpack (expected 1)
In [30]: a = [
...: 1,
...: 2,
...: 3,
...: ]
In [31]: a
Out[31]: [1, 2, 3]
In [32]: def tuple_test(a, b, c,):
...: print(a, b, c,)
...:
In [33]: tuple_test(1,2,3,)
1 2 3
结语
from typing import Union
f: str = "hello, world"
def foo(
x: Union[int, float],
y: Union[int, float],
) -> Union[int, float]:
return x + y
只要事先进行类型注解,那么借助 IDE(我用的是 Pycharm),就能直接暴露出问题或给出相对应的提示:
现在火热的 TypeScript 也是在 JavaScript 的基础上加上了静态类型提示,使得在编译时能够及时发现问题。
可能对于写惯 Python 的人来说多写一些类型注解是那么的繁琐,也有人会觉得用 Python 的最初目的就是能够通过简单的代码来实现其他编译型语言的一些功能,如果还要为这样的动态语言加上一些加锁,那为什么不直接用 Java 或者是 Go 这样的编译型语言呢?
对我个人来说,使用 Python 的初心确实是因为它简单易学好上手,但事上没有十全十美的东西,简单,就意味着要付出代价。至于这个代价是什么,每个人的解读是因人而异。
但为了让自己少掉点头发、少加些班、程序少些 Bug,还是由衷希望协作的同事甚至是每一个 Pythonic 都能采用 Type Hint 的写法,让代码更加健壮的同时也能少踩一些坑。
作者:100gle,练习时长不到两年的非正经文科生一枚,喜欢敲代码、写写文章、捣鼓捣鼓各种新事物;现从事有关大数据分析与挖掘的相关工作。
赞 赏 作 者
Python中文社区作为一个去中心化的全球技术社区,以成为全球20万Python中文开发者的精神部落为愿景,目前覆盖各大主流媒体和协作平台,与阿里、腾讯、百度、微软、亚马逊、开源中国、CSDN等业界知名公司和技术社区建立了广泛的联系,拥有来自十多个国家和地区数万名登记会员,会员来自以工信部、清华大学、北京大学、北京邮电大学、中国人民银行、中科院、中金、华为、BAT、谷歌、微软等为代表的政府机关、科研单位、金融机构以及海内外知名公司,全平台近20万开发者关注。
长按扫码添加“Python小助手”
▼点击成为社区会员 喜欢就点个在看吧