我正在撰写自定义类型Foo
,我希望达到以下目的:写作时
foo = Foo(bar)
然后如果bar
已经是Foo
的实例,则Foo(foo)
会返回该对象未修改,因此foo
和bar
引用同一个对象。否则,应使用来自Foo
的信息以bar
认为合适的任何方式创建Foo.__init__
类型的新对象。
我该怎么做?
我认为Foo.__new__
是关键。如果Foo.__new__
检查成功,则instanceof
返回现有对象应该相当容易,否则请调用super().__new__
。但the documentation for __new__
写道:
如果
__new__()
返回 cls 的实例,则会调用新实例的__init__()
方法,如__init__(self[, ...])
,其中 self 是新实例,其余参数与传递给__new__()
的参数相同。
在这种情况下,我将返回所请求类的实例,尽管不是新实例。我可以以某种方式阻止拨打__init__
吗?或者我是否必须在__init__
内添加一个检查来检测是为新创建的实例还是已经存在的实例调用它?后者听起来像代码重复,应该是可以避免的。
答案 0 :(得分:7)
恕我直言,您应该直接使用__new__
和__init__
。在init中测试是否应该初始化一个新对象或已经有一个现有对象是如此简单,以至于没有代码重复,增加的复杂性是IMHO可接受的
class Foo:
def __new__(cls, obj):
if isinstance(obj, cls):
print("New: same object")
return obj
else:
print("New: create object")
return super(Foo, cls).__new__(cls)
def __init__(self, obj):
if self is obj:
print("init: same object")
else:
print("init: brand new object from ", obj)
# do the actual initialization
它按预期给出:
>>> foo = Foo("x")
New: create object
init: brand new object from x
>>> bar = Foo(foo)
New: same object
init: same object
>>> bar is foo
True
答案 1 :(得分:0)
实现这一目标的一种方法是将所需的代码移动到这样的元类中:
class IdempotentCast(type):
"""Metaclass to ensure that Foo(x) is x if isinstance(x, Foo)"""
def __new__(cls, name, bases, namespace, **kwds):
res = None
defineNew = all(i.__new__ is object.__new__ for i in bases)
if defineNew:
def n(cls, *args, **kwds):
if len(args) == 1 and isinstance(args[0], cls) and not kwds:
return args[0]
else:
return super(res, cls).__new__(cls)
namespace["__new__"] = n
realInit = namespace.get("__init__")
if realInit is not None or defineNew:
@functools.wraps(realInit)
def i(self, *args, **kwds):
if len(args) != 1 or args[0] is not self:
if realInit is None:
return super(res, self).__init__(*args, **kwds)
else:
return realInit(self, *args, **kwds)
namespace["__init__"] = i
res = type.__new__(cls, name, bases, namespace)
return res
class Foo(metaclass=IdempotentCast):
...
除非存在已添加__new__
方法的基类,否则该元类会添加__new__
方法。因此,对于类层次结构,其中某些类扩展了另一个这样的类,__new__
方法将被添加但只有一次。它还包装构造函数以执行检查第一个参数是否与self
相同(感谢the answer Serge Ballesta指出这个简单的检查)。否则,如果没有定义构造函数,它将调用原始构造函数或基础构造函数。
相当多的代码,但您只需要一次,并且可以使用它为您想要的多种类型引入这些语义。如果您只需要一个课程,其他答案可能更合适。