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

Декораторы Python забавны в использовании, но я, кажется, ударил стену из-за того, как аргументы передаются декораторам. Здесь у меня есть декоратор, определенный как часть базового класса (декоратор будет обращаться к членам класса, следовательно, для этого потребуется параметр self).

class SubSystem(object):
    def UpdateGUI(self, fun): #function decorator
        def wrapper(*args):
            self.updateGUIField(*args)
            return fun(*args)
        return wrapper

    def updateGUIField(self, name, value):
        if name in self.gui:
            if type(self.gui[name]) == System.Windows.Controls.CheckBox:
                self.gui[name].IsChecked = value #update checkbox on ui 
            elif type(self.gui[name]) == System.Windows.Controls.Slider:
                self.gui[name].Value = value # update slider on ui 

        ...

Я пропустил оставшуюся часть реализации. Теперь этот класс является базовым классом для различных SubSystems, которые наследуют его - некоторым унаследованным классам необходимо будет использовать декоратор UpdateGUI.

class DO(SubSystem):
    def getport(self, port):
        """Returns the value of Digital Output port "port"."""
        pass

    @SubSystem.UpdateGUI
    def setport(self, port, value):
        """Sets the value of Digital Output port "port"."""
        pass

Я снова опустил реализацию функций, поскольку они не имеют отношения к делу.

Вкратце проблема заключается в том, что, хотя я могу получить доступ к декоратору, определенному в базовом классе, из унаследованного класса, указав его как SubSystem.UpdateGUI, я в конечном итоге получаю этот TypeError при попытке его использования:

unbound method UpdateGUI() must be called with SubSystem instance as first argument (got function instance instead)

Это потому, что я не могу сразу идентифицировать способ передачи параметра self декоратору!

Есть ли способ сделать это? Или я достиг пределов текущей реализации декоратора в Python?

Ответы

Ответ 1

Вам нужно сделать UpdateGUI a @classmethod и сообщить wrapper wrapper о self. Рабочий пример:

class X(object):
    @classmethod
    def foo(cls, fun):
        def wrapper(self, *args, **kwargs):
            self.write(*args, **kwargs)
            return fun(self, *args, **kwargs)
        return wrapper

    def write(self, *args, **kwargs):
        print(args, kwargs)

class Y(X):
    @X.foo
    def bar(self, x):
        print("x:", x)

Y().bar(3)
# prints:
#   (3,) {}
#   x: 3

Ответ 2

Лучше просто вытащить декоратор из класса SubSytem: (Обратите внимание, что я предполагаю, что self, который вызывает setport, является тем же самым self, который вы хотите использовать для вызова updateGUIField.)

def UpdateGUI(fun): #function decorator
    def wrapper(self,*args):
        self.updateGUIField(*args)
        return fun(self,*args)
    return wrapper

class SubSystem(object):
    def updateGUIField(self, name, value):
        # if name in self.gui:
        #     if type(self.gui[name]) == System.Windows.Controls.CheckBox:
        #         self.gui[name].IsChecked = value #update checkbox on ui 
        #     elif type(self.gui[name]) == System.Windows.Controls.Slider:
        #         self.gui[name].Value = value # update slider on ui 
        print(name,value)

class DO(SubSystem):
    @UpdateGUI
    def setport(self, port, value):
        """Sets the value of Digital Output port "port"."""
        pass

do=DO()
do.setport('p','v')
# ('p', 'v')

Ответ 3

Вы как-то ответили на вопрос, спрашивая его: какой аргумент вы ожидаете получить как self, если вы вызываете SubSystem.UpdateGUI? Нет очевидного экземпляра, который должен быть передан декоратору.

Есть несколько вещей, которые вы могли бы сделать, чтобы обойти это. Может быть, у вас уже есть subSystem, который вы создали где-то в другом месте? Затем вы можете использовать его декоратор:

subSystem = SubSystem()
subSystem.UpdateGUI(...)

Но, возможно, вам не нужен экземпляр в первую очередь, просто класс subSystem? В этом случае используйте декоратор classmethod, чтобы сообщить Python, что эта функция должна принимать свой класс как первый аргумент вместо экземпляра:

@classmethod
def UpdateGUI(cls,...):
    ...

Наконец, возможно, вам не нужен доступ к экземпляру или классу! В этом случае используйте staticmethod:

@staticmethod
def UpdateGUI(...):
    ...

Кстати, соглашение Python заключается в том, чтобы зарезервировать имена CamelCase для классов и использовать имена mixedCase или under_scored для методов этого класса.

Ответ 4

Вам нужно использовать экземпляр SubSystem для украшения или использовать classmethod, как подсказывает Кенни.

subsys = SubSystem()
class DO(SubSystem):
    def getport(self, port):
        """Returns the value of Digital Output port "port"."""
        pass

    @subsys.UpdateGUI
    def setport(self, port, value):
        """Sets the value of Digital Output port "port"."""
        pass

Вы решаете, что делать, решив, хотите ли вы, чтобы все экземпляры подкласса имели один и тот же интерфейс графического интерфейса, или если вы хотите, чтобы разные элементы имели разные интерфейсы.

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

Если у них могут быть разные интерфейсы, вам нужно решить, хотите ли вы представить отличимость с наследованием (в этом случае вы также должны использовать classmethod и вызвать декоратор в подклассах SubSystem), или если это лучше представлены как отдельные экземпляры. В этом случае создайте один экземпляр для каждого интерфейса и вызовите декоратор в этом экземпляре.