Переопределение метода класса в python
контекст
Я пытаюсь иметь некоторые "плагины" (я не уверен, что это правильное определение для этого) для моего кода. Под "плагином" я подразумеваю модуль, который определяет модель (это научный код) таким образом, что его существования достаточно, чтобы использовать его где-либо еще в коде.
Конечно, эти плагины должны следовать шаблону, который использует некоторые модули / функции / классы, определенные в моем коде. Вот небольшой фрагмент соответствующей части моего кода:
# [In the code]
class AllModels():
def __init__(self):
"""
Init.
"""
self.count = 0
def register(self, name, model):
"""
Adds a model to the code
"""
setattr(self, name, model)
self.count += 1
return
class Model():
def __init__(self, **kwargs):
"""
Some constants that defines a model
"""
self.a = kwargs.get("a", None)
self.b = kwargs.get("b", None)
# and so on...
def function1(self, *args, **kwargs):
"""
A function that all models will have, but which needs:
- to have a default behavior (when the instance is created)
- to be redefinable by the "plugin" (ie. the model)
"""
# default code for the default behavior
return
instance = AllModels()
и вот соответствующая часть "плагина":
# [in the plugin file]
from code import Model, instance
newmodel = Model(a="a name", b="some other stuff")
def function1(*args, **kwargs):
"""
Work to do by this model
"""
# some specific model-dependent work
return
instance.register(newmodel)
Дополнительная информация и требования
function1
имеет абсолютно одинаковую сигнатуру для любого модельного плагина, но обычно выполняет свою работу для каждого из них.Я хотел бы поведение по умолчанию для
function1
так что, если это не определено плагином, я все равно смогу что-то сделать (попробовать другие возможности и / или выдать предупреждение / ошибку).В плагине
function1
может использовать некоторые другие функции, которые определены только в этом плагине. Я утверждаю это, потому что код работает с модулем многопроцессорности, и мне нужноinstance
экземплярAllModels
уметь звонитьfunction1
в дочерних процессах.instance
определен в родительском процессе, а также в подключаемых модулях модели, но будет использоваться в других дочерних процессах (однако в него не вносятся никакие изменения).было бы здорово, что
function1
, когда "переопределено" плагином, иметь возможность доступа к атрибутамModel
экземпляр (т.е.self
).
проблема
Я прочитал много разных источников документации по Python и некоторые вопросы SO. Я вижу только два / три возможных решения этой проблемы:
1) не декларирует function1
метод в Model
класс, но просто установите его в качестве атрибута, когда плагин создает новый экземпляр этого.
# [in the plugin file]
def function1(*args, **kwargs):
# ....
return
newmodel.function1 = function1
и затем звоните, когда это необходимо. В этом случае атрибут function1
в объекте Model
будет инициировать None
наверное. Одним из предостережений этого является то, что нет "поведения по умолчанию" для function1
(это должно быть рассмотрено в коде, например, тестирование if instance.function1 is None: ...
), и еще большее, что я не могу получить доступ self
сюда...
2) использовать как-то декораторы питона. Я никогда не использовал это, и документация, которую я прочитал, не так проста (я имею в виду не прямо из-за огромного количества возможностей его использования). Но, похоже, это хорошее решение. Однако меня беспокоит его влияние на производительность (я читал, что это может замедлить выполнение декорированной функции / метода). Если это решение является лучшим вариантом, то я хотел бы знать, как его использовать (возможно, быстрый фрагмент), и если возможно использовать атрибуты класса Model
:
# [in the plugin file]
@mydecorator
def function1(self, *args, **kwargs):
"""
I'm not sure I can use *self*, but it would be great since some attributes of self are used for some other function similar to *function1*...
"""
# some stuff using *self*, eg.:
x = self.var **2 + 3.4
# where self.var has been defined before, eg.: newmodel.var = 100.
3) используя модуль types
И его MethodType
... я не уверен, что это актуально в моем случае... но я могу ошибаться.
Как вы, вероятно, видите после этого длинного вопроса, я не очень знаком с такими возможностями Python, и мое понимание декораторов сейчас очень плохое. Продолжая читать некоторую документацию, я подумал, что, возможно, стоит задать этот вопрос здесь, так как я не уверен, какое направление выбрать для решения моей проблемы.
Решение
Прелесть ответа Сендерла в том, что он действительно прост и очевиден... И упустить это - позор. Извините за загрязнение SO с этим вопросом.
3 ответа
Ну, если я не ошибаюсь, вы хотите подкласс Model
, Это похоже на создание экземпляра Model
и заменить его function1
атрибут с функцией, определенной в модуле плагина (т.е. ваш вариант 1); но это намного чище, и заботится обо всех деталях для вас:
# [in the plugin file]
from code import Model, instance
class MyModel(Model):
def function1(*args, **kwargs):
"""
Work to do by this model
"""
# some specific model-dependent work
return
newmodel = MyModel(a="a name", b="some other stuff")
instance.register(newmodel)
Таким образом, все остальные методы (функции "привязаны" к Model
экземпляр) наследуются от Model
; они будут вести себя точно так же, но function1
будет переопределен, и будет следовать вашим индивидуальным function1
определение.
То, что вы пытаетесь сделать, может быть сделано довольно простыми способами - просто используя методы Objected Oriented и пользуясь тем, что в функциях Python также есть обычные объекты -
Одна простая вещь, которую нужно сделать, это просто заставить ваш класс "model" принять "function1" в качестве параметра и сохранить его как член объекта.
Некоторый код, подобный этому, с минимальными изменениями в вашем коде - хотя гораздо более интересные вещи, безусловно, возможны:
# [In the code]
class AllModels():
def __init__(self):
"""
Init.
"""
self.count = 0
def register(self, name, **kwargs):
"""
Adds a model to the code
"""
model = Model(**kwargs)
setattr(self, name, model)
self.count += 1
return
class Model():
def __init__(self, **kwargs):
"""
Some constants that defines a model
"""
self.a = kwargs.get("a", None)
self.b = kwargs.get("b", None)
if "function1" in kwargs:
self.real_function1 = kwargs["function1"]
self.function1.__doc__ = kwargs["function1"].__doc__
# and so on...
def function1(self, *args, **kwargs):
"""
A function that all models will have, but which needs:
- to have a default behavior (when the instance is created)
- to be redefinable by the "plugin" (ie. the model)
"""
if self.real_function1:
return self.real_function1(self, *args, **kwargs)
# default code for the default behavior
return
instance = AllModels()
а также
# [in the plugin file]
from code import Model, instance
newmodel = Model(a="a name", b="some other stuff")
def function1(self, *args, **kwargs):
"""
Work to do by this model
"""
# some specific model-dependent work
return
instance.register("name", function1 = function1, a="a name", b="some other stuff")
Не могли бы вы написать манекен function1()
функция в Model
класс и raise
NotImplementedError
? Таким образом, если кто-то пытается унаследовать от Model
без реализации function1()
, они получат исключение при попытке запустить код. Если вы запускаете код для них, вы можете перехватить эту ошибку и вернуть пользователю полезное сообщение об ошибке.
Например:
class Model:
#Your code
def function1():
raise NotImplementedError("You need to implement function1
when you inherit from Model")
Затем вы можете сделать следующее при запуске кода:
try:
modelObj.function1()
except NotImplementedError as e:
#Perform error handling here
РЕДАКТИРОВАТЬ: Официальная документация Python для NotImplementedError гласит: "В определенных пользователем базовых классах абстрактные методы должны вызывать это исключение, когда им требуются производные классы для переопределения метода". Это, кажется, соответствует требованиям здесь.