扩展现有第三方库的现有类功能的最佳方法是什么?

时间:2017-02-09 19:53:43

标签: python oop inheritance

让我们举例来说,我在我的代码中使用了很多pandas.DataFrames。这是一个非常大的类,有很多部分,它有一个非常类似于函数的API,你倾向于将它的方法调用链接在一起。

感觉能够轻松地为现有类添加功能,同时仍保持功能强大,流畅的API,这样会很好。

我希望将此类转换为特定于域的内容,以便删除在使用此类时经常使用的许多特定于域的样板代码。

让我们说在我的梦中我能够做到这样的事情:

pandas.read_csv('sales.csv') \
    .filter(items=['one', 'three']) \
    .apply(myTransformationFunction) \
    .saveToHivePartition(tablename = 'sales', partitionColumn = 'four') \
    .join(pandas.read_csv('employees.csv')) \
    .filter(items=['one','three','five']) \
    .saveToHivePartition(tablename = 'EmployeeMetrics', partitionColumn = 'ten')

在这个例子中,saveToHivePartition是一个自定义方法,可以在正确的位置以原子方式将某些内容保存到HDFS,然后将该信息添加到hive metadatastore中。超级有用!

显然,“简单”的答案是简单地创建一个独立的函数,我可以将DataFrame对象与我需要的其他参数一起传递。每次我想执行保存时,我都需要将数据框封装到一个变量中,然后将该变量传递给一个单独的函数。没有上面的例子那么干净!

那么接下来要想到的是创建一个很酷的新SuperDataFrame类,它是DataFrame的超集。这种方法的唯一问题是:你如何构建它?我无法改变pandas.read_csv()开始返回我的新班级。我不能将基类强制转换为子类。我可以让SuperDataFrame成为...数据帧类的包装器?但是我觉得任何时候我想调用一个基础DataFrame函数,它必须是这样的:

SuperDataFrame(pandas.read_csv('sales.csv')) \
    .df.filter .... \
    .df.apply .... \
    .saveToHivePartition .... \
    .df.join .... \
    .df.filter .... \
    .saveToHivepartition ....

我甚至认为这不起作用,因为这假设每当pandas执行一个函数时,就会异常改变DataFrame,我很确定它甚至不会这样做。

有什么想法吗?或者这只是一件坏事吗?

2 个答案:

答案 0 :(得分:1)

您可以使用constructor-override properties确保pandas操作返回新类的实例。一个简单的例子:

class MyDF(pandas.DataFrame):
    @property
    def _constructor(self):
        return MyDF

    def myMethod(self):
        return "I am cool"

>>> d = MyDF([[1, 2, 3], [4, 5, 6], [7, 8, 9]], columns=["A", "B", "C"])
>>> d.filter(["A", "B"]).apply(lambda c: c**2).myMethod()
'I am cool'

当然,如果您还希望能够处理Series,那么您还必须创建自己的Series子类并可能定义custom data slots

那就是说,我不清楚你的例子真的值得这样保证。你已经可以在普通的DataFrame上链接方法调用了,所以如果你想要做的只是保存一些中间阶段,那就不那么繁重了:

data = pandas.read_csv('sales.csv') \
    .filter(items=['one', 'three', 'five']) \
    .apply(myTransformationFunction)

data2 = data.join(pandas.read_csv('employees.csv')) \
    .filter(items=['one', 'three'])

saveToHivePartition(data, tablename='sales', partitionColumn='four')
saveToHivePartition(data2, tablename='EmployeeMetrics', partitionColumn='ten')

(由于我在问题评论中提到的问题,我改变了过滤器的顺序。首先过滤到较小的子集而稍后过滤较大的子集是没有意义的;较大的子集赢了&# 39;稍后会因为其中一些已经过滤掉了。)

将事物作为方法链写作的能力本身并不具有优势。我会特别怀疑像.saveToHivePartition这样的链接方法,这些方法可能只是因为它们的副作用而被调用。当链接方法以流水线方式运行时,它们是有意义的,每个方法都接受前一个方法的输入并修改它以传递给下一个方法。但是,让方法只产生副作用并返回未更改的对象并不会使代码更具可读性。

另请注意,此解决方案特定于熊猫。通常,如果某些库中的类创建了彼此的实例,则必须仔细设计库以允许以保留类之间关系的方式进行子类化。 Pandas用我描述的构造函数覆盖机制完成了这个,但它并不总是这样,在此之前,很难做到你想要做的事情。

答案 1 :(得分:0)

一种方法是扩展DataFrame类。为了保持流畅的界面,您可以创建自己的custom.read_csv

def read_csv(file):
    dataframe = pandas.read_csv(file)
    return SuperDataFrame(dataframe)

首先调用该函数,其余的函数调用保持不变。您只需在新数据框中添加所需的功能即可。

或者(特定于python的hack),您可以将自己的函数 monkey-patch 直接导入到导入它的模块中的原始数据框类中,但不要......