Python中重置类的首选方法

32

基于CodeReview上的这篇文章

我有一个Python(3)中的Foo类,其中当然包括一个__init__()方法。这个类会触发一些提示并完成它的任务。假设我想要能够重置Foo,以便我可以重新开始整个过程。

什么是首选的实现方式?

调用__init__()方法。

def reset(self):
    self.__init__()

创建新实例还是使用现有实例?

def reset(self):
    Foo()

我不确定创建Foo的新实例是否会留下任何可能影响性能的东西,如果调用reset多次会怎样。另一方面,如果没有在__init__()中定义(重新定义)所有属性,则__init__()可能会产生副作用。

有没有更好的方法来做到这一点?


2
你做错了。没有“重置类”模式是有原因的:当你完成一个类实例中的数据时,你应该处理掉那个实例并创建另一个实例。 - jsbueno
@jsbueno 如果我没记错的话,第二个选项就是这样做的。 - JAD
不是真的 - 如果您希望仅有一个类实例,这些是要做的事情,但这很少需要(如果需要,应在 __new__ 方法中处理)。只需创建一个新实例 - 如果没有对先前实例的其他引用,则它将自然丢弃。换句话说:正常的代码不会“手动”调用 __init__ - 只需创建一个新实例并允许语言完成即可。 - jsbueno
3
我想指出,“创建并且释放新实例”的方法有时会导致内存使用量激增,特别是在像Python这样的语言中,你没有办法通过像.Dispose()或.Free()这样的函数清理自己的垃圾,并且必须依赖垃圾回收器。想象一下,如果您需要创建数千万次该类的实例,或者只创建一个并重复使用,那么不仅内存使用量将有天壤之别,而且也会更快,因为每次都不需要进行新的内存分配。所以,请不要采用“一种方法适用于所有”的教学方式。 - Tuncay Göncüoğlu
在重置方法中重新初始化self,如果您有一个使用init更改的类变量,则会产生问题。假设该类跟踪已创建多少个对象,并且在__init __()中将1添加到num,那么逻辑上重置不等于创建。 - Kaleba KB Keitshokile
显示剩余3条评论
4个回答

22

两种都是正确的,但语义实现方式不同。

为了能够重置一个实例,我会这样写(我更喜欢从__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() 只会设置本地引用,该引用也将在方法结束时超出作用域(并且新实例将被销毁)。


4
这种方法是首先想到的自然解决方案,但是... 尽管在Python中它确实是有效的并且可以工作,但许多IDE在代码完成时会出现困难,因为它们只在__init__中查找类成员。只是一个注意点。 - Tuncay Göncüoğlu
我已经使用这种方法有一段时间了,但是我认为当你有类继承时会存在问题。假设子类在调用super().reset()后还有一些额外的重置任务要执行。您不能将self.reset()添加到子类__init__方法的末尾,因为这样超级类的重置将被调用两次。我找到的唯一解决方案是在子类__init__方法的末尾使用super().__init__()调用,但这并不总是理想的。有人知道这种情况下更好的解决方案吗? - Bill
我一直在__init__中使用self.reset(),但今天我发现这会让pylint生气,特别是出现了attribute-defined-outside-init错误。它真的希望所有类变量在构造函数中都有self.varname = value的赋值语句。 - erco

6

你可以将你使用的实例保存在一个类属性中。 每当你想要重置这个类时,重新分配一个新的实例给这个属性。 以下是我实现此方法的方式:

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() 重置实例,并且 cls 参数会自动传递给该方法。
我更喜欢这种方法(更或多或少是单例模式),而不是您提出的那些方法,因为在您的情况下,似乎有一个 Foo 的单个实例是合理的,因为您想要进行重置。 因此,我认为强制使用单个实例相当直观。
另一方面,您可以在类外部拥有一个实例,并定义一个 reset 实例方法:
def reset(self):
    self.__init__()

但这种方法可能效果不佳。比如说,如果你想在__init__方法之外设置属性,那么调用__init__将不会重置那些属性。因此,你的实例将不会按预期被重置。如果你持有一个单一的实例并将其重新分配给一个全新的实例,你可以确信它会是完全干净的。
关于你所谓的“创建一个新实例”,我选择的也大致是这样,但问题是要把它存储在哪里。我认为把它保存在类本身中是有意义的。
顺便说一下,这种解决方案不应该存在任何性能问题(如“内存泄漏”),因为每次只有一个Foo实例被引用,而创建一个新实例会取消引用以前的实例。

1
我之前没有这样想过。您能否评论一下为什么/如果这比我在问题中提到的方法更可取? - JAD
当然。谢谢! - JAD
从代码审查问题中可以看出,这个操作很有意义,所以感谢您的解释! - Calvin Ellington

2
我自己也有类似的问题,发现最好的方法是:
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

现在你有一个reset()方法,当你想要重置所有属性时,你可以在MyClass的实例上调用它。

然而,如果你有一个数据类,你需要这样做:

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

0

正如其他人所说,您不需要重置一个类。但如果您真的想要这样做,您可以使用一个工厂函数,在每次调用时返回一个新的类。

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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接