Сохраняйте локальные переменные в области действия при расширении метода

#python #oop

Вопрос:

Допустим, у меня есть класс TT , наследуемый от класса T и расширяющий поведение одного из его методов, например:

 class T:
    def __init__(self, p, b):
        self.p = p
        self.b = b
        self.fun()
        
    def fun(self):
        a = self.p   self.b
        print(a)
        

class TT(T):
    def __init__(self, p, b):
        super().__init__(p, b)
        
    def fun(self):
        super().fun()
        c = self.p*self.b
        print(c)
 

То , что я хотел бы иметь, — это TT.fun полагаться на переменные T.fun , определенные a в этом примере. Две очевидные вещи , которые пришли мне на ум T.fun , — это вернуть a или сделать a переменную экземпляра, но я не нахожу ни то, ни другое удовлетворительным. Есть ли способ TT.fun поделиться областью применения метода, который он расширяет?


Редактировать

Спасибо за ответы.

Чтобы расширить мой вариант использования, мои производные классы выполняют некоторую оптимизацию вектора для набора данных. Родительский класс здесь для того, чтобы предложить общий, согласованный интерфейс для различных оптимизаций. В функции класса, подлежащей оптимизации, некоторые шаги (в зависимости от текущего состояния вектора) разделяются всеми подклассами, поэтому я решил поместить ее в родительский метод. И, как предполагает Лагербер, они уже выполнялись в эквиваленте «частного» compute_a() метода; я имел в виду вызвать этот метод, чтобы вывести результаты в область действия родительского класса, поэтому мне не пришлось бы делать это в отдельных подклассах. Оказывается, учитывая небольшое количество подклассов, с которыми я имею дело, дублирование нескольких строк кода может быть самой простой вещью, которую здесь можно сделать.

Что касается предложений Натаниэля Форда, вот почему я не нахожу свои первоначальные решения удовлетворительными:

  • Мне не нужно это значение после запуска оптимизации, и оно меняется на каждом шаге оптимизации, поэтому я не думаю, что имеет смысл сохранять его в качестве переменной экземпляра.
  • Возврат значения приведет к тому, что расширенный метод будет вести себя иначе, чем его родитель (в смысле сигнатуры).
  • Я не хочу, чтобы что-то задерживалось в глобальном масштабе.

Как вы отметили, мое мышление не было ориентировано на инкапсуляцию; такое напоминание полезно время от времени иметь!

Комментарии:

1. «Есть ли способ для TT.fun поделиться областью применения метода, который он расширяет?» нет, нет, это не так.

Ответ №1:

Нет простого, не хакерского способа. (И я даже не знаю, есть ли какой-нибудь хитрый способ).

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

Наследование не является механизмом, нарушающим этот принцип.

В вашем конкретном примере проблема связана с плохим дизайном интерфейса T . Если T бы у вас был метод compute_a() , который возвращался self.p self.b бы, то в вашем унаследованном классе вы, конечно, могли бы позвонить self.compute_a() .

Но делайте это только в том случае, если a это больше, чем просто внутренняя деталь реализации!

Ответ №2:

Короткий ответ- «нет», в частности, потому, что тот факт, что подкласс имеет функцию с именем, совпадающим с именем в родительском классе, не означает, что эти функции имеют что-либо общее. Когда вы вызываете super().fun() , вы вызываете fun() родительский класс, но это то же самое, что вызывать fun() совершенно не связанный класс — за исключением того, что родительский класс разделяет его состояние (переменные экземпляра).

Таким образом, единственный способ для экземпляра подкласса получить доступ к вычислениям, выполняемым в функции экземпляра родительского класса,-это использовать обычные шаблоны:

  • Сохранение результата в состояние экземпляра (переменные экземпляра).
  • Возвращая значение, такое, что super().fun() становится a = super().fun()
  • Сохранение в какое-либо другое значение более глобальной области (не рекомендуется).

Когда вы говорите «ни один из способов не является удовлетворительным», вам, вероятно, следует копнуть немного глубже и изучить, почему это так. Существуют альтернативы, такие как частные методы, которые могли бы решить то, что кажется беспорядочной ситуацией, чистым способом. Тем не менее, ваш пример не демонстрирует, почему он fun() даже перегружен, или чего вы надеетесь достичь, поделившись областью действия.

Ответ №3:

Вы можете просто вернуть значение из fun() родительского класса, а затем присвоить возвращаемое значение переменной внутри дочернего метода. Это, безусловно, работает, но я не думаю, что это хороший дизайн:

 class T:
    def __init__(self, p, b):
        self.p = p
        self.b = b
        self.fun()
        
    def fun(self):
        a = self.p   self.b
        return a
        

class TT(T):
    def __init__(self, p, b):
        super().__init__(p, b)
        
    def fun(self):
        a = super().fun()
        c = self.p * self.b * a
        return c

t = T(2, 3)
tt = TT(2, 3)
print(t.fun())   # >> 5
print(tt.fun())  # >> 30

 

Комментарии:

1. Одно важное замечание здесь заключается в том, что, хотя вы можете это сделать, вы должны быть уверены, что возвращаемое из T.fun() того же типа, TT.fun() что и . Если a и c означают разные вещи, все станет довольно запутанным.