在 __new__ 中进行初始化工作是“原罪”吗?如果是这样,如何最好地处理单例的运行一次初始化?
Posted
技术标签:
【中文标题】在 __new__ 中进行初始化工作是“原罪”吗?如果是这样,如何最好地处理单例的运行一次初始化?【英文标题】:Is it an 'original sin' to do init work in __new__? If so, how best to handle run once init for singletons? 【发布时间】:2012-03-21 05:04:15 【问题描述】:我有一个具有我使用的标准 python 单例形式的类。它还初始化一个值,但不是在每个实例中都初始化它,而是在我进行单例处理时在 __new__()
中初始化它。这是pythonic的方式吗?如果没有,我还能怎么办?
class Foo(object):
_inst = None
def __new__(cls, val):
if Foo._inst is None:
Foo._inst = super(Foo, self).__new__(cls)
Foo._inst.val = val
return Foo._inst
这显然是简化的形式。我已经使用了等待__init__()
的构造来检查并设置Foo._inst
来处理单例的运行一次初始化,但这对于多线程情况下的同步错误似乎已经成熟。如果再次运行__init__()
,则在现有实例中没有可能损坏的值的情况下,将 init 放入__init__()
并没有什么害处,除非您正在运行的代码在您不这样做时实际上什么都不做不需要,但我不想覆盖通常会损坏的状态值。我知道单例也是邪恶的,但有时,它们是最好的解决方案(比如定义特殊用途类型,我希望能够通过 is
进行检查)。
这似乎是我能想到的最佳解决方案,但我觉得在 __new__()
中初始化实例值很脏。有没有人知道如何更好地处理这个问题?
【问题讨论】:
使用单例是原罪。它们是一种为全局变量添加额外缺点和奇怪限制的方法。如果您需要全局状态,请使用全局变量,但最好尽量避免使用它们。永远不要使用单例。 @Sven Marnach 当然,我尽我所能避免单例和全局,但在某些情况下它们是最好的解决方案。例如,我刚刚实现了一个三元类,但我不需要每种类型的多个实例(True、False、Unknown),实际上,只有一个实例让我可以像使用布尔值一样使用它,因为它允许is
运营商工作。因此,虽然要避免单例,但当它们是最佳选择时,应该有很好的方法来使用它们。
你不是说“大罪”吗?
使用博格。 (它看起来像一个普通的类,但实例字典存储在类上,所以每个实例实际上都一样。)
另外你不应该在布尔值上使用is
。例如,[] is not False
但空列表为布尔值 false。
【参考方案1】:
您始终可以使用全局变量而不是单例。具有三个不同实例的类的示例:
class _Ternary(object):
"""Internal class. Don't instantiate!"""
def __init__(self, value):
self.value = value
ternary_0 = _Ternary(0)
ternary_1 = _Ternary(1)
ternary_2 = _Ternary(2)
def ternary(value):
"""Factory function to request a "ternary" instance."""
return 0: ternary_0, 1: ternary_1: 2: ternary_2[value]
这是直截了当的,可以避免单身人士的痛苦。
【讨论】:
但是使用这种模式如何使操作符重载工作得很好呢?例如,如何让ternary_1 - ternary_1
返回ternary_0
?
@GrantJ:我不明白这个问题。在Ternary
类上定义__sub__
方法?【参考方案2】:
回答第一个问题: 单例在 Python 中并不经常使用,因为它们并不是真正需要的。
回答第二个: 如果你认为你真的需要它们,有时会使用一种更简洁、类似的模式,称为“Borg”,请参阅:the Borg design pattern 如果您只需要在实例之间共享一些属性并且不需要成熟的单例(可以实例化一次),您可以简单地使用类属性,如下所示:
class Foo( object ):
val = ...
def __init__( self ):
...
那么所有实例将共享相同的“val”属性值。
【讨论】:
我的例子可能过于简单了......在更常见的例子中,我实际上在类中存储了一个实例字典,我的目标是限制特殊类型实例的数量。所以你最终得到Foo._insts = 1 : Foo(1), 2 : Foo(2), 3 : Foo(3)
。然后,您最终会得到 3 个值的唯一类型,但每个值只有一个实例。
Python 不是一种允许任何如此严格的语言,即无论如何你可能无法 100% 保护自己。但是,如果您只是想为此创建一个像样的接口,为什么不创建两个类,其中一个是寄存器,如果没有超过限制,则允许创建其他的实例?即 Foo() 和 FooRegistry() 并有一个方法 FooRegistry.create() 如果没有超过限制,则返回 Foo 实例,否则会引发异常,或者做其他事情来处理这种情况......?
为什么要添加另一层类来做同样的事情?这会将 FooRegistery 和 Foo 折叠到一个类中,其中 FooRegistery.create() 变成 Foo.__new__()。
因为我们认为它不太优雅和 pythonic,请参阅@Sven Marnach 对您的问题的评论。此外,这些类应该映射到您的问题域中的某种逻辑对象。有了你所说的,你显然在这里有两个对象 - Foo,以及某种跟踪 Foos 的注册表,那么为什么不让它变得简单(KISS 原则)和干净呢?
@sr2222:如果你认为你的例子过于简单,你可以edit你的问题并更新它/改进它/添加更多有用的信息。在那里说明您的要求和实际代码,这样我们就可以真正帮助您/告诉您是否有问题。【参考方案3】:
没有更好的方法来创建单例。如果你打算这样做(三值逻辑是我能想到的唯一一个有意义的例子),那么你可以按照你的方式去做。
【讨论】:
是的,这就是我使用 Python Tribool 数据类型的用例。以上是关于在 __new__ 中进行初始化工作是“原罪”吗?如果是这样,如何最好地处理单例的运行一次初始化?的主要内容,如果未能解决你的问题,请参考以下文章
JS可否 var a[id] = new init();创建对象数组、进行赋值?