将装饰的DataFrame与所有pandas函数相关联

时间:2015-04-06 15:50:12

标签: python pandas decorator monkeypatching

我想为我的DataFrame添加一个唯一的ID,我基本上成功地使用了我在这里找到的Python Class Decorator。我在这里https://github.com/pydata/pandas/issues/2485知道添加自定义元数据尚未明确支持,但装饰器似乎是一种解决方法。

当我使用copy和groupby.agg等方法时,我装饰的DataFrames返回新的和类似装饰的DataFrame。如何让“全部”pandas函数(如pd.DataFrame()或pd.read_csv返回我的装饰DataFrames而不是原始的,未修饰的DataFrames而不单独装饰每个pandas函数?即,如何让我的装饰DataFrames替换库存DataFrames?

这是我的代码。首先,我有一个增强的pandas模块wrapPandas.py。

from pandas import *
import numpy as np

def addId(cls):

    class withId(cls):

        def __init__(self, *args, **kargs):
            super(withId, self).__init__(*args, **kargs)
            self._myId = np.random.randint(0,99999)

    return withId

pandas.core.frame.DataFrame = addId(pandas.core.frame.DataFrame)

当我使用.copy()和.groupby()。agg()等方法时,运行以下代码片段会显示我的DataFrame返回修饰的DataFrame。然后我会通过显示像pd.DataFrame这样的pandas函数不返回我装饰的DataFrames来进行跟进(遗憾的是,这并不奇怪)。

编辑:根据Jonathan Eunice的回复添加了进口声明。

import wrapPandas as pd

d = {
    'strCol': ['A', 'B', 'A', 'C', 'B', 'B', 'A', 'C', 'A'], 
    'intCol': [6,3,8,6,7,3,9,2,6], 
}

#create "decorated" DataFrame
dfFoo = pd.core.frame.DataFrame.from_records(d)
print("dfFoo._myId = {}".format(dfFoo._myId))

#new DataFrame with new ._myId
dfBat = dfFoo.copy()
print("dfBat._myId = {}".format(dfBat._myId))

#new binding for old DataFrame, keeps old ._myId
dfRat = dfFoo
print("dfRat._myId = {}".format(dfRat._myId))

#new DataFrame with new ._myId
dfBird = dfFoo.groupby('strCol').agg({'intCol': 'sum'})
print("dfBird._myId = {}".format(dfBird._myId))

#all of these new DataFrames have the same type, "withId"
print("type(dfFoo) = {}".format(type(dfFoo)))

这会产生以下结果。

dfFoo._myId = 66622
dfBat._myId = 22527
dfRat._myId = 66622
dfBird._myId = 97593
type(dfFoo) = <class 'wrapPandas.withId'>

悲伤的部分。 dfBoo._myId当然会提出AttributeError

#create "stock" DataFrame
dfBoo = pd.DataFrame(d)
print(type(dfBoo))

#doesn't have a ._myId (I wish it did, though)
print(dfBoo._myId)

1 个答案:

答案 0 :(得分:1)

将您的猴子补丁修改为:

pd.DataFrame = pandas.core.frame.DataFrame = addId(pandas.core.frame.DataFrame)

即。所以你是&#34;锁定&#34;或者&#34;猴子补丁&#34;两个不同的名字。 鉴于pandas.core.frame.DataFrame is pd.DataFrame,这种双重分配的需要可能看起来很奇怪。但是你实际上并没有修改DataFrame类。您正在注入代理类。无论对代理的引用是什么。直接到原始类的那些没有获得代理行为。通过让您可能想要使用的所有名称指向代理来更改它。 以下是图形化的方式:

diagram

我假设您的文件中某处import pandas as pd未显示,但dfBoo的定义会因NameError: name 'pd' is not defined而失败。

由于这样的原因,猴子修补很危险。你注射了东西......而且你不可能知道你是否抓住了所有的参考文献&#34;或者&#34;修补你需要的一切。&#34;我不能保证在代码中不会有其他调用,这些调用的地址低于此名称重新组合的结构。但是对于显示的代码,它的工作原理!

更新您稍后询问了如何为pd.read_csv开展此工作。嗯,这可能是你需要修补的另一个地方。在这种情况下,请将上面的补丁代码修改为:

pd.DataFrame = pandas.io.parsers.DataFrame = pandas.core.frame.DataFrame = addId(pandas.core.frame.DataFrame)

DataFrame内修补pandas.io.parsers.DataFrame的定义将为read_csv提供帮助。同样的警告适用:可能有(也可能是)更多用途,您需要追踪全面覆盖。