Python - расширяющие свойства, такие как расширение функции

Вопрос

Как вы можете расширить свойство python?

Подкласс может распространять функцию суперкласса, вызывая его в перегруженной версии, а затем работает с результатом. Вот пример того, что я имею в виду, когда говорю "расширение функции":

# Extending a function (a tongue-in-cheek example)

class NormalMath(object):
    def __init__(self, number):
        self.number = number

    def add_pi(self):
        n = self.number
        return n + 3.1415


class NewMath(object):
    def add_pi(self):
        # NewMath doesn't know how NormalMath added pi (and shouldn't need to).
        # It just uses the result.
        n = NormalMath.add_pi(self)  

        # In NewMath, fractions are considered too hard for our users.
        # We therefore silently convert them to integers.
        return int(n)

Существует ли аналогичная операция для расширения функций, но для функций, которые используют декоратор свойств?

Я хочу сделать некоторые дополнительные вычисления сразу после получения дорогостоящего атрибута. Мне нужно, чтобы атрибут доступа был ленивым. Я не хочу, чтобы пользователь вызывал специальную процедуру для выполнения вычислений. в основном, я не хочу, чтобы пользователь всегда знал, что вычисления были сделаны в первую очередь. Однако атрибут должен оставаться свойством, так как у меня есть старый код, который мне нужно поддерживать.

Может быть, это работа для декораторов? Если я не ошибаюсь, декоратор - это функция, которая обертывает другую функцию, и я ищу, чтобы обернуть свойство еще несколькими вычислениями, а затем представить его как свойство снова, что похоже на аналогичную идею... но я не может это понять.

Моя конкретная проблема

У меня есть базовый класс LogFile с дорогим конструктором .dataframe. Я реализовал его как свойство (с помощью декоратора свойств), поэтому он фактически не будет анализировать файл журнала, пока я не попрошу фреймворк данных. Пока это отлично работает. Я могу создать объекты LogFile (100+) LogFile и использовать более дешевые методы для фильтрации и выбора только важных для синтаксического анализа. И всякий раз, когда я использую один и тот же LogFile снова и снова, мне нужно только разобрать его при первом доступе к фреймворку данных.

Теперь мне нужно написать подкласс LogFile, SensorLog, который добавляет некоторые дополнительные столбцы в атрибут dataframe базового класса, но я не могу понять синтаксис, чтобы вызвать конструкцию DataFrame суперкласса (не зная ничего о своей внутренней работе), затем оперируйте полученным фреймворком данных, а затем кешируйте/возвращайте его.

# Base Class - rules for parsing/interacting with data.
class LogFile(object):
    def __init__(self, file_name):
        # file name to find the log file
        self.file_name = file_name
        # non-public variable to cache results of parse()
        self._dataframe = None

    def parse(self):
        with open(self.file_name) as infile:
            ...
            ...
            # Complex rules to interpret the file 
            ...
            ...
        self._dataframe = pandas.DataFrame(stuff)

    @property
    def dataframe(self):
        """
        Returns the dataframe; parses file if necessary. This works great!

        """
        if self._dataframe is None:
            self.parse()
        return self._dataframe

    @dataframe.setter
    def dataframe(self,value):
        self._dataframe = value


# Sub class - adds more information to data, but does't parse
# must preserve established .dataframe interface
class SensorLog(LogFile):
    def __init__(self, file_name):
        # Call the super constructor
        LogFile.__init__(self, file_name)

        # SensorLog doesn't actually know about (and doesn't rely on) the ._dataframe cache, so it overrides it just in case.
        self._dataframe = None

    # THIS IS THE PART I CAN'T FIGURE OUT
    # Here my best guess, but it doesn't quite work:
    @property
    def dataframe(self):
        # use parent class getter, invoking the hidden parse function and any other operations LogFile might do.
        self._dataframe = LogFile.dataframe.getter()    

        # Add additional calculated columns
        self._dataframe['extra_stuff'] = 'hello world!'
        return self._dataframe


    @dataframe.setter
    def dataframe(self, value):
        self._dataframe = value

Теперь, когда эти классы используются в интерактивном сеансе, пользователь должен иметь возможность взаимодействовать с тем же способом.

>>> log = LogFile('data.csv')
>>> print log.dataframe
#### DataFrame with 10 columns goes here ####
>>> sensor = SensorLog('data.csv')
>>> print sensor.dataframe
#### DataFrame with 11 columns goes here ####

У меня есть много существующего кода, который использует экземпляр LogFile, который предоставляет атрибут .dataframe и предлагает что-то интересное (в основном, для построения). Я бы ЛЮБЛЮ иметь экземпляры SensorLog, представляющие один и тот же интерфейс, чтобы они могли использовать один и тот же код. Возможно ли расширить прокси-сервер данных суперкласса, чтобы воспользоваться существующими подпрограммами? Как? Или мне лучше сделать это по-другому?

Спасибо, что прочитал эту огромную стену текста. Вы интернет-герой, дорогой читатель. Есть идеи?

+9
источник поделиться
4 ответа

Вы должны вызывать свойства суперкласса, а не обходить их через self._dataframe. Вот общий пример:

class A(object):

    def __init__(self):
        self.__prop = None

    @property
    def prop(self):
        return self.__prop

    @prop.setter
    def prop(self, value):
        self.__prop = value

class B(A):

    def __init__(self):
        super(B, self).__init__()

    @property
    def prop(self):
        value = A.prop.fget(self)
        value['extra'] = 'stuff'
        return value

    @prop.setter
    def prop(self, value):
        A.prop.fset(self, value)

И используя его:

b = B()
b.prop = dict((('a', 1), ('b', 2)))
print(b.prop)

Выходы:

{'a': 1, 'b': 2, 'extra': 'stuff'}

Обычно я рекомендую размещать побочные эффекты в сеттерах вместо геттеров, например:

class A(object):

    def __init__(self):
        self.__prop = None

    @property
    def prop(self):
        return self.__prop

    @prop.setter
    def prop(self, value):
        self.__prop = value

class B(A):

    def __init__(self):
        super(B, self).__init__()

    @property
    def prop(self):
        return A.prop.fget(self)

    @prop.setter
    def prop(self, value):
        value['extra'] = 'stuff'
        A.prop.fset(self, value)

Также следует избегать дорогостоящих операций внутри получателя (например, ваш метод анализа).

+8
источник

Если я правильно понимаю, что вы хотите сделать, это вызвать родительский метод из дочернего экземпляра. Обычный способ сделать это - использовать встроенный super.

Я прикрепил ваш пример с языком и изменил его, чтобы использовать super, чтобы показать вам:

class NormalMath(object):
    def __init__(self, number):
        self.number = number

    def add_pi(self):
        n = self.number
        return n + 3.1415


class NewMath(NormalMath):
    def add_pi(self):
        # this will call NormalMath add_pi with
        normal_maths_pi_plus_num = super(NewMath, self).add_pi()
        return int(normal_maths_pi_plus_num)

В примере вашего Журнала вместо вызова:

self._dataframe = LogFile.dataframe.getter() 

вы должны позвонить:

self._dataframe = super(SensorLog, self).dataframe

Подробнее о супер здесь

Изменить. Даже подумал, что пример, который я дал вам, касающийся методов, сделать то же самое с @properties не должен быть проблемой.

+1
источник

У вас есть возможность рассмотреть:

1/Наследовать от logfile и переопределить parse в вашем производном классе датчиков. Должно быть возможно изменить ваши методы, которые работают на dataframe, работать независимо от количества членов, которые dataframe имеет - поскольку вы используете pandas, многие из них выполняются для вас.

2/Создайте sensor экземпляр logfile, затем предоставите свой собственный метод синтаксического анализа.

3/Обобщите parse и, возможно, некоторые из ваших других методов, чтобы использовать список дескрипторов данных и, возможно, словарь методов/правил, установленных в инициализаторе класса или заданных с помощью методов.

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

Лично я думаю, что вы найдете преимущества вариантов 3 или 4, которые будут самыми мощными.

+1
источник

Проблема заключается в том, что вам не хватает самостоятельного перехода в родительский класс. Если ваш родитель является синглом, тогда должен работать @staticmethod.

class X():
    x=1
    @staticmethod
    def getx():
        return X.x

class Y(X):
    y=2
    def getyx(self):
        return X.getx()+self.y

wx = Y()
wx.getyx()
3
0
источник

Посмотрите другие вопросы по меткам или Задайте вопрос