【问题标题】:Class name as idempotent typecast function类名作为幂等类型转换函数
【发布时间】:2017-01-27 09:02:25
【问题描述】:

我正在编写一个自定义类型Foo,我想实现以下目标:编写时

foo = Foo(bar)

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

我该怎么做?

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

如果__new__()返回一个cls的实例,那么新实例的__init__()方法将像__init__(self[, ...])一样被调用,其中self是新实例并且其余参数与传递给 __new__() 的参数相同。

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

【问题讨论】:

  • 不可以用工厂方法来实现吗?
  • @Tagc:我有点习惯于使用构造函数名称进行强制转换。例如float(x)str(x)tuple(x),如果它已经是正确的类型,则返回相同的对象。我经常使用这种表示法,所以我也希望我自己的类型也能保持一致。所以这是风格问题,我猜。从功能上讲,工厂方法当然也可以正常工作。

标签: python python-3.x constructor casting


【解决方案1】:

恕我直言,您应该直接使用__new____init__。在 init 中测试是否应该初始化一个新对象或已经有一个现有对象非常简单,没有代码重复,而且增加的复杂性是恕我直言可以接受的

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

【讨论】:

  • self is arg 模式很整洁,没想到。我想我最终还是会为此编写一个元类,以便将此检查自动引入派生类的构造函数中。
【解决方案2】:

实现这一点的一种方法是将所需的代码移动到这样的元类中:

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 相同(感谢Serge Ballestathe answer 指出这个简单的检查)。否则,它调用原始构造函数,如果没有定义构造函数,则调用基构造函数。

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-02-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-04-10
    相关资源
    最近更新 更多