Preferred way of resetting a class in Python
Asked Answered
B

4

32

Based on this post on CodeReview.

I have a class Foo in Python (3), which of course includes a __init__() method. This class fires a couple of prompts and does its thing. Say I want to be able to reset Foo so I can start the procedure all over again.

What would be the preferred implementation?

Calling the __init__() method again

def reset(self):
    self.__init__()

or creating a new instance?

def reset(self):
    Foo()

I am not sure if creating a new instance of Foo leaves behind anything that might affect performance if reset is called many times. On the other hand __init__() might have side-effects if not all attributes are (re)defined in __init__().

Is there a preferred way to do this?

Bethesda answered 21/8, 2017 at 13:53 Comment(8)
"You are doing it wrong". There is no "reset class" pattern for a reason: when you are done with the data in a class instance, you dispose of that instance and create another one.Discobolus
@Discobolus Which is what the second option does, if I am not mistaken.Bethesda
Not really - these are things to do if by some reason you'd like to have only one instance ever of your class - that is seldon needed (and if it were needed, it should be taken care of in the __new__ method). Just create a new instance - if there are no other references to the previous instance, it will be discarded naturally. In other words: normal code does not call __init__ "manually" - just create a new instance and allow the language do that.Discobolus
I'd like to point out that "create and dispose new instance" approach sometimes results in a memory usage explosion, especially in languages like python where you have no way to clean up after yourself with a function like .Dispose() or .Free() and have to trust garbage collector. Imagine you need to create thousands, millions of that class, or just one and reuse. Not only would the memory usage differ astronomically, it would also be faster because there is no new memory allocation each time. So... please refrain from "one method covers all" lessons.Table
Re-initialising self in the reset method would be bad if you have a class variable that is changed with the init. Let us say the class keeps track of how many objects have been created with num and in __init__() you add 1 to num, then logically resetting isn't the same as creating.Vehicle
@Discobolus I'm not sure "just create a new instance" is a convenient approach. If instantiation involves passing many parameters, then re-doing it again later in code runs the risk of not re-initializing an identical instance. Being able to call foo.reset() to reset all variables (but not the constant parameters) is a convenient and common use case (i.m.o.).Mylonite
@Bill: it sure has uses, but it is far from the most common pattern. For a complex service with a lot of site-effects, even different kinds of 'reseting' make sense. But for simple, record-style objects, with no side-effects, creating new ones should be the rule - and of course there are tons of cases where one might want to do otherwise. In Python you may think on calling a dict's .clear() vs creating a new dict: just see how often each gets used in normal code.Discobolus
@Discobolus If the instantiation is computationally expensive, for example when initializing a dynamic model that will be simulated over time, then a reset method makes a lot of sense. Rather than re-build the model each time you want to run a new simulation, you only reset the dynamic variables.Mylonite
S
24

Both are correct but the semantics are not implemented the same way.

To be able to reset an instance, I would write this (I prefere to call a custom method from __init__ than the opposite, because __init__ is a special method, but this is mainly a matter of taste):

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

You use it that way:

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

Creating a new instance from scratch is in fact simpler because the class has not to implement any special method:

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

the old instance will be garbage collected if it is not used anywhere else

Both way can be used for normal classes where all initialization is done in __init__, but the second way is required for special classes that are customized at creation time with __new__, for example immutable classes.


But beware, this code:

def reset(self):
    Foo()

will not do what you want: it will just create a new instance, and immediately delete it because it will go out of scope at the end of the method. Even self = Foo() would only set the local reference which would the same go out of scope (and the new instance destroyed) at the end of the methos.

Salon answered 21/8, 2017 at 15:56 Comment(3)
This method is the natural solution that comes to mind first, but... Even tho it is indeed valid in python and does work, many IDE's will have trouble with code completion since they look for class members only in init. Just a note.Table
I've been using this method for a while but I think there is a problem when you have class inheritance. Suppose the child class has some additional resetting tasks to do after calling super().reset(). You can't add self.reset() to the end of the sub-class __init__ method because then the super-class reset would be called twice. The only solution I've found is to have the super().__init__() call at the end of the sub-class __init__ method, but this is not always ideal. Anyone know a better solution in this case?Mylonite
I've been using a self.reset() in __init__, but I found today this makes pylint angry, specifically with attribute-defined-outside-init errors. It really wants self.varname = value in the ctor for all class variables.Statolatry
C
6

You could hold the instance you work with in a class attribute. Whenever you want to reset the class, reassign a new instance to that attribute. Here is how I would implement this approach:

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

Then, you can access the instance by simply referring to Foo.instance.

I chose to have the reset as a class method, thus decorated by @classmethod. With this decorator, the instance can be reset by calling Foo.reset(), and the cls parameter will be passed automatically to the method.

I prefer this approach (which is more or less a singleton pattern) over those you suggest, because in your situation, it appears logical to have a single instance of Foo, since you want to reset it. Therefore, I find it rather intuitive to "force" the use of a single instance.

On the other hand, you could have an instance outside of the class, and use a reset instance method, defined:

def reset(self):
    self.__init__()

But this might not work so well. Say you want to set attributes outside of the __init__ method. Calling __init__ will not reset those attributes. Therefore, your instance will not be reset as expected. Now if you hold a single instance and reassign it to a brand new one, you're certain that it will be absolutely clean.

Regarding what you call "creating a new instance", that's more or less what I chose, but the question is where to store it. I think it makes sense to keep it warm in the class itself.

By the way, there shouldn't be any performance issue (as in "memory leak") with this solution, since only one Foo instance is referenced at a time, and creating a new one will de-reference the previous one.

Chigetai answered 21/8, 2017 at 14:8 Comment(3)
I hadn't thought of it this way. Could you perhaps comment on why/if this is preferable to the methods I mentioned in the question?Bethesda
Definitely. Thanks!Bethesda
OP from the code review question, this makes a lot of sense so thank you for the explanation!Gimmal
W
2

I had a similar question myself and had discovered that the best way to do it is:

class MyClass:

    def __init__(self):
        self.reset()

    def reset(self):
        self.my_attribute_1 = 0
        self.my_attribute_2 = None
        self.my_attribute_3 = False

Now you have a reset() method which you can call on an instance of MyClass whenever you want to reset all those attributes.

However, if you have a dataclass, you need to do it like this:

from dataclasses import dataclass

@dataclass
class MyClass:

    def __post_init__(self):
        self.reset()

    def reset(self):
        self.my_attribute_1 = 0
        self.my_attribute_2 = None
        self.my_attribute_3 = False
Write answered 2/6, 2022 at 22:35 Comment(0)
L
0

As others said, you don't need to reset a class. But if you really want it, you can use a factory function that returns a new class for every call.

def make_Foo():
    class Foo():
        VALUE = 1
    return Foo

Foo = make_Foo()
Foo.VALUE = 2
print(Foo.VALUE)  # prints 2

Foo = make_Foo()  # reset
print(Foo.VALUE)  # prints 1
Lytic answered 21/8, 2017 at 13:53 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.