Is it an 'original sin' to do init work in __new__? If so, how best to handle run once init for singletons?
我有一个类,我使用标准的python单例形式。它也只是一个值,但不是在每个实例中初始化它,当我进行单例处理时,我在__new__()中初始化它。这是pythonic方式吗?如果没有,我还能怎么做?
1 2 3 4 5 6 7
| 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放在__init__()中没有任何害处,除非您运行的代码在您不执行时无效地执行任何操作我需要,但经常有腐败的状态值,我不想覆盖。我知道单身人士也是邪恶的,但有时候,他们是最好的解决方案(比如定义我希望能够用is检查的特殊用途类型)。
这似乎是我能提出的最佳解决方案,但我觉得在__new__()中初始化实例值很脏。有没有人有关于如何更好地处理这个问题的想法?
-
使用单身人士是一种原罪。它们是为全局变量添加额外缺点和奇怪限制的一种方法。如果你需要全局状态,请使用全局变量,但最好尽量避免它们。永远不要使用单身人士。
-
@Sven Marnach我尽我所能避免单身人士和全球人,但有些情况下他们是最好的解决方案。例如,我刚刚实现了一个Ternary类,但我不需要每种类型的一个实例(True,False,Unknown),实际上,每个类型中只有一个允许我使用它更像bool is运营商工作。因此,虽然要避免单身人士,但当他们是最佳选择时,应该有很好的方法来使用它们。
-
你不是指"基数罪"吗?
-
使用博格。 (它看起来像普通类,但实例字典存储在类中,因此每个实例实际上都是相同的。)
-
此外,您不应该在布尔值上使用is。例如,[] is not False但空列表是布尔值false。
-
@katielalex这就是我在真实代码中所做的事情,但为了简单起见,我只编写了代码,说明了我在这里遇到的问题。是的,他们都用bool()评估为False,但有时你确实想知道差异。
-
@katrielalex,+1有趣的命名模式和简洁的描述。 :-)
-
@SvenMarnach我的代码中没有__init__ ...
-
@Norla我是程序员,不是神学家。 ;)
-
这个问题似乎与这个答案有关(最后有点提到is比较)
-
@SvenMarnach哦,是的,谢谢。错过了...编辑。
-
@ Rik Poggi是的,它是相关的,只是因为在我的代码之后,我在我的代码中使用了这样的东西。但是,在__new __()中进行初始化的一般情况更为通用,我在此线程中讨论的问题也是如此。
您始终可以使用全局变量而不是单例。 具有三个不同实例的类的示例:
1 2 3 4 5 6 7 8 9 10 11 12
| 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__方法?
回答第一个问题:
单例在Python中并不经常使用,因为它们并不是真正需要的。
回答第二个问题:
如果您认为自己确实需要它们,那么有时会使用稍微清洁,类似"Borg"的模式,请参阅:Borg设计模式
如果你只需要在实例之间共享一些属性而不需要完整的单例(可以实例化一次),你可以简单地使用类属性,如下所示:
1 2 3 4 5
| 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原则)和干净?
-
当然,我不是想攻击你,我只是提出替代方式:)
-
如果你想变得迂腐,你已经有了2个对象,类型为foo的对象,以及作为Foo实例的对象...;)
-
我通常不那么迂腐:)
-
@ sr2222:如果你认为你的例子是横向扩展的,你可以编辑你的问题并更新它/改进它/添加更多有用的信息。说明你的要求和你的实际代码,这样我们就能真正帮助你/告诉你是否有什么问题。
没有更好的方法来创造单身人士。 如果你想要它(并且三值逻辑是关于我能想到的唯一例子),那么你就可以按照自己的方式去做。
-
是的,这是我用于Python Tribool数据类型的用例。