Наследование от классов с ** и без ** kwargs - PullRequest
0 голосов
/ 04 июля 2018

Я строю класс построения в Python и надеюсь сделать следующее. Мне нужно графическое окно, использующее PyQt5, которое также наследует некоторые сделанные мной пользовательские классы (например, класс подгонки кривой). Чтобы класс подбора кривой мог манипулировать данными, сохраняющимися в классе построения, он должен иметь ссылку на данные, содержащиеся в классе построения. Из-за этого я выбрал класс печати для наследования от класса CurveFitting.

Проблема, похоже, возникает в наследовании как от класса GraphicsWindow PyQt5, так и от моего пользовательского класса, которые принимают разное количество аргументов. Я читал, что Python не очень хорошо работает с классами, которые наследуют различное количество аргументов, используя «супер» функциональность, поэтому я решил сделать так, чтобы мой собственный класс CurveFitting принимал ** kwargs, что затем дало бы ему ссылку на родителя. Однако затем я столкнулся с другой ошибкой, которую я не понимаю. Ниже приведен пример того, что я пытаюсь сделать

import numpy as np
from pyqtgraph import GraphicsWindow

class ClassA():
    def __init__(self, **kwargs):
        super().__init__()
        self.kwargs = kwargs
        self.parent = self.kwargs['parent']
        self.xdata = self.parent.xdata

    def print_data(self):
        print(self.parent.xdata)
        print(self.parent.ydata)


class classC(GraphicsWindow, ClassA):
    def __init__(self):
        kwargs = {}
        kwargs['parent'] = self
        kargs = kwargs
        self.xdata = np.linspace(0, 100, 101)
        self.ydata = np.linspace(0, 200, 101)

        super().__init__(**kwargs)
        # ClassA.__init__(self, **kwargs)
        # GraphicsWindow.__init__(self)

instC = classC()
instC.print_data()

Когда я запускаю вышеупомянутое, я получаю «RuntimeError: супер-класс init () типа classC никогда не вызывался» в строке «super () .__ init (** kwargs)», которую я Честно говоря, совсем не понимаю, и пытался гуглить некоторое время, но безрезультатно.

Кроме того, я попытался закомментировать строку и раскомментировать следующие две строки, чтобы наследовать от каждого класса вручную, но это также не работает. Что я нахожу довольно странным, так это то, что, если я прокомментирую одну из этих двух строк, они оба работают по отдельности, но вместе они не работают. Например, если я запускаю его с обеими строками, выдает ошибку, что у kwargs нет ключевого слова «parent», как если бы он даже не передавал ** kwargs.

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

1 Ответ

0 голосов
/ 04 июля 2018

Непосредственная проблема с вашим кодом заключается в том, что ClassC наследуется от GraphicsWindow в качестве первого базового класса, а ClassA - второго базового класса. Когда вы вызываете super, вызывается только один (GraphicsWindow), и если он не предназначен для работы с множественным наследованием (как это имеет место), он может не вызывать super сам по себе или может не передавать аргументы, которые ClassA ожидает.

Простого переключения порядка базовых классов может быть достаточно, чтобы заставить его работать. Python гарантирует, что базовые классы будут вызываться в том же относительном порядке, в котором они появляются в операторе class (хотя другие классы могут быть вставлены между ними в MRO, если позднее произойдет больше наследования). Поскольку ClassA.__init__ вызывает super, он должен работать лучше!

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

Но код, который у вас есть, делает работу с аргументами ключевых слов немного сложнее, чем должно быть. Вам не нужно явно создавать словари для передачи с синтаксисом **kwargs, а также не нужно извлекать значения ключевых слов из условия, принятого вами с аргументом **kwargs. Обычно каждая функция должна называть аргументы, которые она принимает, и использовать **kwargs только для неизвестных аргументов (которые могут понадобиться другому классу в MRO). Вот как это выглядит:

class Base1:
    def __init__(self, *, arg1, arg2, arg3, **kwargs):  # the * means the other args are kw-only
        super().__init__(**kwargs)                      # always pass on all unknown arguments
        ...                                             # use the named args here (not kwargs)

class Base2:
    def __init__(self, *, arg4, arg5, arg6, **kwargs):
        super().__init__(**kwargs)
        ...

class Derived(Base1, Base2):
    def __init__(self, *, arg2, arg7, **kwargs):         # child can accept args used by parents
        super().__init__(arg2=arg2+1, arg6=3, **kwargs)  # it can then modify or create from
        ...                                              # scratch args to pass to its parents

obj = Derived(arg1=1, arg2=2, arg3=3, arg4=4, arg5=5, arg7=7) # note, we've skipped arg6
                                                              # and Base1 will get 3 for arg2

Но я бы также серьезно отнесся к тому, имеет ли наследство смысл в вашей ситуации. Для одного из двух базовых классов может иметь смысл инкапсулироваться в дочерний класс, а не наследоваться от. То есть вы наследуете только от одного из ClassA или GraphicsWindow и сохраняете экземпляр другого в каждом экземпляре ClassC. (Вы могли бы даже наследовать ни от одного базового класса, и инкапсулировать их оба.) Инкапсуляцию часто намного проще рассуждать и получать права, чем наследование.

...