Предпочтительный способ сброса класса в Python

на основе этот пост на CodeReview.

у меня есть класс Foo в Python (3), который, конечно, включает в себя __init__() метод. Этот класс запускает пару подсказок и делает свое дело. Скажем, я хочу иметь возможность сбросить Foo таким образом, я могу начать процедуру снова и снова.

что было бы предпочтительной реализацией?

вызов __init__() метод снова

def reset(self):
    self.__init__()

или создание нового экземпляра?

def reset(self):
    Foo()

Я не уверен, что создание нового экземпляра Foo оставляет позади все, что может повлиять на производительность, если reset вызывается много раз. С другой стороны!--3--> может иметь побочные эффекты, если не все атрибуты (re)определены в __init__().

есть ли предпочтительный способ сделать это?

2 ответов


оба правильны, но семантика не реализована одинаково.

чтобы иметь возможность сбросить экземпляр, я бы написал это (я предпочитаю вызывать пользовательский метод из __init__ чем наоборот, потому что __init__ это особый метод, но это в основном дело вкуса):

class Foo:
    def __init__(self):
        self.reset()
    def reset(self):
        # set all members to their initial value

вы используете его таким образом:

Foo foo      # create an instance
...
foo.reset()  # reset it

создание нового экземпляра с нуля на самом деле проще, потому что класс не должен реализовывать какие-либо специальные метод:

Foo foo      # create an instance
...
foo = Foo()  # make foo be a brand new Foo

старый экземпляр будет собирать мусор, если он не используется нигде больше

оба пути можно использовать для нормальный классы, где вся инициализация выполняется в __init__, но второй способ требуется для специальных классов, которые настраиваются во время создания с помощью __new__, например неизменяемые классы.


но будьте осторожны, этот код:

def reset(self):
    Foo()

будет не делайте, что хотите: это будет просто создайте новый экземпляр и немедленно удалите его, потому что он выйдет из области действия в конце метода. Даже self = Foo() установит только локальную ссылку, которая будет выходить за рамки (и новый экземпляр будет уничтожен) в конце methos.


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

class Foo:
    instance = None    # The single instance

    def __init__(self, ...):
        # Initialize the instance if Foo.instance does not exist, else fail
        if type(self).instance is None:
            # Initialization
            type(self).instance = self
        else:
            raise RuntimeError("Only one instance of 'Foo' can exist at a time")

    @classmethod
    def reset(cls):
        cls.instance = None        # First clear Foo.instance so that __init__ does not fail
        cls.instance = Foo(...)    # Now the initialization can be called

затем вы можете получить доступ к экземпляру, просто обратившись к Foo.instance.

я выбрал reset как метод класса, таким образом украшенный @classmethod. С помощью этого декоратора экземпляр можно сбросить, вызвав Foo.reset() и будет автоматически передается методу.

я предпочитаю этот подход (который является более или менее одноэлементным шаблоном) над теми, которые вы предлагаете, потому что в вашей ситуации кажется логичным иметь один экземпляр Foo, так как вы хотите сброс его. Поэтому я нахожу довольно интуитивным "форсировать" использование одного экземпляра.

С другой стороны, вы можете иметь экземпляр вне класса и использовать reset метод экземпляра , определено:

def reset(self):
    self.__init__()

но это может не так хорошо работать. Скажем, вы хотите установить атрибуты вне __init__ метод. Зову __init__ не сбрасывает эти атрибуты. Поэтому ваш экземпляр не будет сброшен, как ожидалось. Теперь, если вы держите один экземпляр и переназначаете его на совершенно новый, Вы уверены, что он будет абсолютно чистым.

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

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