在python中创建幂等初始化器的正确方法是什么?幂等的意思是,如果对象x
是类A
的实例,则A(x) is x
(如正则表达式,frozenset,字符串和元组一样)。我可以想到三种解决方案:
覆盖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
隐藏初始化函数后面的实际类
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)
创建一个元类
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):
...
在我看来,所有这些解决方案似乎都是骇人听闻的,每个都有明显的缺点。规范的方法是什么?
答案 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