在Python中创建包装类时,如何使父类的方法返回我的包装类实例
比如说,如果我在一个 pandas 数据框(dataframe)上创建一个简单的包装类:
from pandas import DataFrame
class MyDataFrame(DataFrame):
def __init__(self,data):
DataFrame.__init__(self,data)
@staticmethod
def add(a, b):
return a + b
然后我实例化它,也就是创建一个这个包装类的具体对象……
x = MyDataFrame([1,2,3])
x.add(1,2)
# => 3
type(x)
# => __main__.MyDataFrame
这样是可以正常工作的。但是,如果我在这个对象上调用一个返回数据框的方法,它就不再是我这个包装类的实例了。
y = x.reindex([3,4,5])
type(y)
# => pandas.core.frame.DataFrame
我该怎么做才能让所有数据框的方法都返回 MyDataFrame 的实例呢?这是一个常见的问题吗?我这样做是不是有问题?
2 个回答
0
在Pandas这个库里,有几个地方的类设计得不太好,导致后续的类无法很好地继承。有些问题已经解决了,比如在这两个链接里可以看到的内容:https://github.com/pydata/pandas/pull/4271 和 https://github.com/pydata/pandas/issues/60。
我们可以实现一个父类的 reindex
方法,这样返回的结果就是一个子类的实例:
from pandas import DataFrame
class DF():
def __init__(self, data):
print('DF __init__')
self.data = data
def reindex(self, index):
print('DF reindex')
return self.__class__(self.data)
# return DF(self.data) # not like this!
class MyDF(DF):
def __init__(self, data):
DF.__init__(self, data)
@staticmethod
def add(a, b):
return a + b
x = MyDF([1,2,3])
x.add(1,2)
# => 3
type(x)
y = x.reindex([3,4,5])
type(y)
z = DF([1,2,3])
type(z.reindex([1, 2]))
在Pandas的新版本中,内部设置了一个叫 `_constructor` 的东西,用来控制返回的类型。设置这个类属性似乎可以解决问题:
class MyDataFrame(DataFrame):
def __init__(self, *args, **kwargs):
DataFrame.__init__(self, *args, **kwargs)
@staticmethod
def add(a, b):
return a + b
MyDataFrame._constructor = MyDataFrame
>>> type(y)
<class '__main__.MyDataFrame'>
2
你展示的例子其实不是一个包装器,而是Python中的一个子类。关于Python的子类和方法解析,在你的情况下,它们遵循一些简单的规则。
- 首先查看调用方法的对象的
type
。 - 检查这个类的层级结构,找到第一个定义该方法的地方。然后查看这个方法的签名,并按照这个签名来执行。在你的例子中,类的层级结构很简单,就是子类和父类。
所以,在你的例子中,
x
被定义为MyDataFrame
类的一个对象——这很简单。显然,type(x)
就是MyDataFrame
,这是定义好的。- 在调用
add
方法时,它查看接收对象,也就是x
,它属于MyDataFrame
类。而这个类实际上定义了add
方法。所以,它直接返回这个方法的结果。有趣的是,试试调用DataFrame([1, 2, 3]).add(1, 2)
。结果会不同,因为它查看的是在pandas.DataFrame
类中定义的add
方法。 - 接下来是第三部分——我们继续用同样的逻辑。
reindex
在MyDataFrame
中没有定义。那我们该往哪里找呢?看类的层级结构,也就是pandas.DataFrame
。确实,这个类定义了reindex
,而且它返回的是一个pandas.DataFrame对象!(查看这个链接:http://pandas.pydata.org/pandas-docs/dev/generated/pandas.DataFrame.reindex.html#pandas.DataFrame.reindex)所以,y
是一个pandas DataFrame
也就不奇怪了。
现在我不太明白你为什么要在一开始就扩展pandas DataFrame。这样扩展并不是一种常见的做法。如果你能提供更多你想做的事情的细节,也许我们可以给出解决方案。
编辑:你最初的问题是关于扩展方法或扩展对象(C#有这些,正如你所说,JS的原型也提供了相同的功能。Python并没有将扩展方法/对象作为第一类成员。对此有过讨论。例如:python扩展方法)