类名作为幂等类型转换函数

时间:2017-01-27 09:02:25

标签: python python-3.x constructor casting

我正在撰写自定义类型Foo,我希望达到以下目的:写作时

foo = Foo(bar)

然后如果bar已经是Foo的实例,则Foo(foo)会返回该对象未修改,因此foobar引用同一个对象。否则,应使用来自Foo的信息以bar认为合适的任何方式创建Foo.__init__类型的新对象。

我该怎么做?

我认为Foo.__new__是关键。如果Foo.__new__检查成功,则instanceof返回现有对象应该相当容易,否则请调用super().__new__。但the documentation for __new__写道:

  

如果__new__()返回 cls 的实例,则会调用新实例的__init__()方法,如__init__(self[, ...]),其中 self 是新实例,其余参数与传递给__new__()的参数相同。

在这种情况下,我将返回所请求类的实例,尽管不是新实例。我可以以某种方式阻止拨打__init__吗?或者我是否必须在__init__内添加一个检查来检测是为新创建的实例还是已经存在的实例调用它?后者听起来像代码重复,应该是可以避免的。

2 个答案:

答案 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指出这个简单的检查)。否则,如果没有定义构造函数,它将调用原始构造函数或基础构造函数。

相当多的代码,但您只需要一次,并且可以使用它为您想要的多种类型引入这些语义。如果您只需要一个课程,其他答案可能更合适。