Декораторы 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
), или если это лучше представлены как отдельные экземпляры. В этом случае создайте один экземпляр для каждого интерфейса и вызовите декоратор в этом экземпляре.