首页 > 解决方案 > 我可以定义如何处理访问 python 对象(而不仅仅是它的属性)吗?

问题描述

我在 python 中有一个自定义类,如果对象本身(即,如果它的方法/属性不是一个)被访问,我想以某种方式表现。

这是一个人为的最小工作示例来说明我的意思。我有一个包含各种pandas DataFrames 的类,以便可以单独操作它们:

import pandas as pd
import numpy as np

class SplitDataFrame:
    
    def __init__(self, df0, df1):
        self._dfs = [df0, df1]
        
    def increase(self, num, inc):
        self._dfs[num] = self._dfs[num] + inc
        
    @property
    def asonedf(self):
        return pd.concat(self._dfs, axis=1)


d = SplitDataFrame(pd.DataFrame(np.random.rand(2,2), columns=['a','b']),
                   pd.DataFrame(np.random.rand(2,2), columns=['q','r']))
d.increase(0, 10)

这行得通,我可以检查一下,d._dfs现在确实是

[           a          b
 0  10.845681  10.561956
 1  10.036739  10.262282,
           q         r
 0  0.164336  0.412171
 1  0.440800  0.945003]

到目前为止,一切都很好。

现在,我想更改/添加到类的定义,以便在不使用该.increase方法时返回连接的数据帧。换句话说,在访问时d,我希望它返回与键入时相同的数据框d.asonedf,即

           a          b         q         r
0  10.143904  10.154455  0.776952  0.247526
1  10.039038  10.619113  0.443737  0.040389

这样,对象更紧密地遵循pandas.DataFrameapi:

那可能吗?

我可以SplitDataFrame从继承pandas.DataFrame,但这并不能神奇地添加所需的行为。

非常感谢!

标签: python

解决方案


您当然可以根据需要将所有相关的魔术方法代理到连接的数据帧。如果你不想无休止地重复自己,你可以动态地做到这一点。

我不是说这是要走的路,但它有点工作:

import textwrap

import pandas as pd
import numpy as np


class SplitDataFrame:
    def __init__(self, df0, df1):
        self._dfs = [df0, df1]

    def increase(self, num, inc):
        self._dfs[num] = self._dfs[num] + inc

    for name in dir(pd.DataFrame):
        if name in (
            "__init__",
            "__new__",
            "__getattribute__",
            "__getattr__",
            "__setattr__",
        ) or not callable(getattr(pd.DataFrame, name)):
            continue
        exec(
            textwrap.dedent(
                f"""
                def {name}(self, *args, **kwargs):
                    return pd.concat(self._dfs, axis=1).{name}(*args, **kwargs)
                """
            )
        )

正如您可能猜到的那样,此解决方案附带各种字符串,并且使用了可怕的做法(使用exec,使用dir,...)。

至少我会实现__repr__这样你就不会对自己撒谎这是什么类型的对象,也许你想显式枚举你想要定义的所有方法,而不是通过dir(). 而不是exec()您可以正常定义函数,然后使用setattr.


推荐阅读