Переопределение метода класса в python - PullRequest
5 голосов
/ 23 марта 2012

Контекст

Я пытаюсь добавить в свой код несколько "плагинов" (я не уверен, что это правильное определение для этого). Под «плагином» я подразумеваю модуль, который определяет модель (это научный код) таким образом, что его существования достаточно, чтобы использовать его где-либо еще в коде.

Конечно, эти плагины должны следовать шаблону, который использует некоторые модули / функции / классы, определенные в моем коде. Вот небольшой фрагмент соответствующей части моего кода:

# [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).

Проблема

Я прочитал много разных источников документации по питону и несколько 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) используя как-то декораторы python. Я никогда не использовал это, и документация, которую я прочитал, не так проста (я имею в виду не прямо из-за огромного количества возможностей его использования). Но, похоже, это хорошее решение. Однако меня беспокоит его влияние на производительность (я читал, что это может замедлить выполнение декорированной функции / метода). Если это решение является лучшим вариантом, то я хотел бы знать, как его использовать (возможно, быстрый фрагмент), и если возможно использовать атрибуты класса 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 ]

5 голосов
/ 23 марта 2012

Ну, если я не ошибаюсь, вы хотите подкласс 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.

2 голосов
/ 23 марта 2012

То, что вы пытаетесь сделать, может быть сделано довольно простыми способами - просто используя методы 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")
1 голос
/ 23 марта 2012

Не могли бы вы написать фиктивную функцию function1() в классе Model и raise a 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

EDIT: Официальная документация Python для NotImplementedError гласит: «В определяемых пользователем базовых классах абстрактные методы должны вызывать это исключение, когда им требуются производные классы дляпереопределить метод. "Это, кажется, соответствует требованиям здесь.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...