Переопределение метода класса в 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 гласит: "В определенных пользователем базовых классах абстрактные методы должны вызывать это исключение, когда им требуются производные классы для переопределения метода". Это, кажется, соответствует требованиям здесь.

Другие вопросы по тегам