前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >pandas作者当年遇到了什么麻烦,才设计出如此糟糕的警告机制

pandas作者当年遇到了什么麻烦,才设计出如此糟糕的警告机制

作者头像
咋咋
发布2023-02-10 13:12:29
3100
发布2023-02-10 13:12:29
举报
文章被收录于专栏:数据大宇宙数据大宇宙

前言

pandas 中有一个让人捉摸不透的警告:

有人说,你用了"链式赋值操作",你应该:

事实上,这样子也会出来警告:

警告信息真的让人无语。有一些无脑的教程会说,你得用 copy:

无缘无故为啥要复制一整份数据。难道每次赋值都要 copy ?

我在 pandas 专栏中也详细讲解了其中的原理,主要是驳斥了网络上一些无脑说法。今天我们换一个角度,尝试成为 pandas 作者,看看当时作者到底遇到了什么样的难题,使得他做出这样子设计。

为什么我不把文章发布在 pandas 专栏中?因为本文涉及的是 python 的非初级知识点


从零开始

假设我们是 pandas 的作者,现在要设计数据表(DataFrame)的定义:

  • 名字叫 MyFrame
  • 初始化的时候需要传入字典数据

看看怎么使用

  • 实例化的时候,传入字典

筛选是数据表常规操作,添加一个 where 的函数:

  • 功能实现不是本文重点,这里借用 pandas 实现
  • 行15:重点是,我们要返回一个全新的 MyFrame 对象。 因为我们不希望后续操作会影响原来的数据

现在可以条件筛选:

现在的问题是,筛选总是用 where 显得太啰嗦。在 python 中对集合的物件取出元素,是有专门的语法 [...]

  • 行2:语法 对象[切片范围]

这不也是一种数据筛选的方式吗?我们自定义的类也能有这样子的语法支持吗?


魔法方法

从对象的角度来说,它只不过是数据与函数的结合体。显然语法中的 [] 应该是一个函数。但 python 中是不可能如下定义函数名字的:

  • 行17:这违反了 python 定义函数名字的规则

python 的作者心想,既然特殊符号不行,那就用比较不常用又合法的函数名字代替吧。所以,集合的物件取出元素的函数名字如下:

  • 行18: __getitem__(self,...) ,就表示当使用 语法 对象[0:-2] 会调用的函数
  • 行25:可以看到,最终仍然是调用之前定义的 where 函数(没必要重新实现一次)。

现在我们的数据表可以这样子使用:

语义感满满。

同样的套路,我们可以实现赋值操作。首先实现一个 update 函数

  • 行39:注意,更新操作不需要返回新的对象,而是修改对象中的 data

重点来了, 索引赋值操作 同样有魔法方法:

  • 行41:魔法方法名字: __setitem__(self, keys, new_value)
  • 行50:直接调用 update 函数即可

看看使用方式就很容易理解这个魔法方法:

  • 行6:使用 update
  • 行7:使用索引赋值

以下是魔法方法调用示意图:

可以想象,当年 pandas 作者做到这一步也是信心满满,这语义牛逼了!但不出意外的话,很快就会出意外!


陷阱

许多初学者以为,索引赋值操作会执行2个步骤( 错误理解 ):

  1. 执行等号左边的筛选操作。调用了魔法方法 __getitem__ ,得到了一个新的数据表
  2. 执行赋值操作

现在我们自己实现了一遍就清楚知道,实际上代码只调用了 __setitem__ 函数, 没有调用 __getitem__ ,因此不会产生任何新的对象。

再看下面的代码:

  • 行7:看起来也是更新操作。但结果根本没有被更新

这里就会执行2个步骤:

  1. 执行等号左边第一个 f1[cond] ,也就是执行了一次 __getitem__ ,返回了一个全新的对象
  2. 全新的对象执行赋值操作 ,执行了一次 __setitem__ ,完成赋值操作。注意,这一步执行的操作,不是作用在 f1 对象上

如果代码换一种写法,就很容易理解:

  • 行5:f2 就是之前说的"新对象"
  • 行7:更新的是 f2 ,f2 也确实被更新。但我们却期望 f1 被更新

此时,pandas 的作者有点绝望了。因为这是 python 的机制,他无法改变。唯一能做的,就是做一个警告,用于提醒用户。

此时他灵机一动,想到了一个简单可行的机制。


警告机制

问题核心就在于:

既然是 __getitem__ 的祸,那就从这里做手脚吧。

首先,在对象初始化的时候,给一个标志属性:

  • 行11:标记一个对象是否为影子对象,就类似之前例子中的 f2

__getitem__ 中,返回全新对象之前,修改新对象的 _shadow 属性:

  • 行36:打标记

接下来就很简单,在 __setitem__ 里面,按需出警告:

  • 行65-66:判断,出警告

实际使用:

这种警告机制的问题在于,大部分情况下,我们会无意识产生 "影子对象" 。比如最常见的一种情况:

  • 行7:做了一次筛选
  • 行10:调用函数
  • 注意结果,是正确的,但仍然出现警告。 这就是为什么在我的 pandas 专栏中明确告诉大家,只要你明确知道需要修改的数据表对象,那就可以不用管这警告

你觉得这种设计思路是不是挺巧妙,同时又让人有点无语?

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2023-02-06,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 数据大宇宙 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言
  • 从零开始
  • 魔法方法
  • 陷阱
  • 警告机制
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档