обеспечить альтернативный API для класса Python через свойство как перенаправление - PullRequest
0 голосов
/ 13 октября 2019

Это общий вопрос разработки Python, но если говорить конкретно, я буду говорить о pandas.DataFrame классе и его unstack методе.

Моя цель - предоставить альтернативную версию unstackОднако я не хочу переопределять исходную версию, а просто предоставить способ доступа к моей версии через .x слой перенаправления:

import pandas as pd
dat = pd.DataFrame(some args)
# call my function
dat.x.unstack()
# call original function
dat.unstack()

Требование к дизайну заключается в том, что реализация моей версии должна быть в состояниичтобы вызвать исходный код unstack.

. Ради этого вопроса можно предположить, что мне разрешено добавить немного кода в pandas.DataFrame, но это добавление должно быть минимальным, поскольку это общедоступный пакет. ,Полностью ненавязчивое решение было бы плюсом.

Ответы [ 2 ]

0 голосов
/ 14 октября 2019

Вот решение, вдохновленное @kingkupps использованием getattr в его ответе.

import pandas as pd


class DataFrame(pd.DataFrame):
    # replace pd.DataFrame.__getattr__
    _super__getattr__ = pd.DataFrame.__getattr__

    def _replacement__getattr__(self, name):
        if name == 'x':
            return DataFrame(self)
        return DataFrame._super__getattr__(self, name)

    pd.DataFrame.__getattr__ = _replacement__getattr__

    # new api
    def unstack(self, *args, **kwargs):
        print('new api')
        return super().unstack(*args, **kwargs)


df = pd.DataFrame(range(5))
print(df.unstack())
print(df.x.unstack())

РЕДАКТИРОВАТЬ: оказалось, что pandas явно предоставляет способ определения таких расширений, см. https://pandas.pydata.org/pandas-docs/stable/development/extending.html

0 голосов
/ 13 октября 2019

Предположим, у вас есть класс с именем X, который имеет все дополнительные методы, которые вы хотите реализовать поверх DataFrame, который выглядит примерно так:

class X(object):

    def __init__(self, df):
        self._df = df

    def unstack(self):
        return 'X.untrack called!'

Вы можете создать оболочку вокруг DataFrame вот так:

class DataFrameWrapper(object):

    def __init__(self, *args, **kwargs):
        self._dataframe = pd.DataFrame(*args, **kwargs)
        self._delegate = X(self._dataframe)

    def __getattr__(self, attr):
        if attr == 'x':
            return self._delegate
        return getattr(self._dataframe, attr)

dfw = DataFrameWrapper([1, 2, 3, 4, 5])
print(dfw.unstack())    # Normal unstack() output
print(dfw.x.unstack())  # X.untrack called!

Вы также можете использовать setattr для установки атрибута x в вашем DataFrame экземпляре для ссылки на экземпляр вашего объекта делегата.

df = pd.DataFrame([1, 2, 3, 4, 5])
setattr(df, 'x', X())
print(df.unstack())    # Normal unstack() output
print(df.x.unstack())  # X.untrack called!

РЕДАКТИРОВАТЬ: Передал фрейм данных в оболочке в конструктор X.

...