Python:创建幂等初始化器

时间:2018-11-26 13:14:43

标签: python python-3.x

在python中创建幂等初始化器的正确方法是什么?幂等的意思是,如果对象x是类A的实例,则A(x) is x(如正则表达式,frozenset,字符串和元组一样)。我可以想到三种解决方案:

  1. 覆盖new

    class A:
      def __new__(cls, *args, **kwargs):
        if not kwargs and len(args) == 1 and isinstance(args[0],cls):
           return args[0]
        return super().__new__(*args, **kwargs)
      def __init__(*args, **kwargs):
        # check that init isn't called on the same object twice
        if not kwargs and len(args) == 1 and isinstance(args[0],cls):
           return
        ... # actual initialization goes here
    
  2. 隐藏初始化函数后面的实际类

    class A:
        ...
    def a(*args, **kwargs):
        if not kwargs and len(args) == 1 and isinstance(args[0], A):
            return args[0]
        return A(*args, **kwargs)
    
  3. 创建一个元类

    class Meta(type):
        def __call__(cls, *args, **kwargs):
            if not kwargs and len(args) == 1 and isinstance(args[0], cls):
                return args[0]
            return super().__call__(*args, **kwargs)
    class A(metaclass = Meta):
        ...
    

    在我看来,所有这些解决方案似乎都是骇人听闻的,每个都有明显的缺点。规范的方法是什么?

1 个答案:

答案 0 :(得分:3)

规范的方法是让__new__返回对象本身。尽管是C代码,但这正是frozenset,字符串和元组的作用。

例如,frozenset.__new__setobject.c frozenset_new(),它使用:

/* frozenset(f) is idempotent */
if (PyFrozenSet_CheckExact(iterable)) {
    Py_INCREF(iterable);
    return iterable;
}

请考虑到它们是不可变类型,因此它们没有定义__init__方法(或C等效方法)。这很重要,因为从__new__返回作为当前类(或子类)实例的对象,将自动触发__init__被调用(如果已定义)。您不想在这里重新初始化对象!

因此正确的实现会将所有内容放入__new__

class A:
    def __new__(cls, *args, **kwargs):
        if not kwargs and len(args) == 1 and isinstance(args[0], cls):
            # A(instance_of_A) is idempotent
            return args[0]

        instance = super().__new__(*args, **kwargs)
        # actual initialization goes here
        return instance

    # **NO** __init__ method for 'immutable' objects