允许默认构造函数接受一个实例并原封不动地返回它

Posted

技术标签:

【中文标题】允许默认构造函数接受一个实例并原封不动地返回它【英文标题】:Allow the deafult constructor to accept an instance and return it unchainged 【发布时间】:2018-08-03 17:39:21 【问题描述】:

我想让我的类构造函数接受这个类的一个实例,在这种情况下返回这个相同的实例而不是创建一个新对象。就像tuple 所做的那样:

>>> t = (1, 2, 3)
>>> tuple(t) is t
True

我想我需要为此重写 __new__ 方法,并在 __init__ 方法中另外处理这种特殊情况。有这方面的食谱吗?

我宁愿完全跳过__init__,当构造函数被赋予一个类实例时,它将原样返回,但我看不到任何办法。对于clssuper的三重使用,我也有点怀疑:

class C:
    @staticmethod
    def __new__(cls, x=None):
        if isinstance(x, cls):
            return x
        else:
            return super(cls, cls).__new__(cls)

    def __init__(self, x=None):
        if x is self: return  # Can I just skip __init__ instead?
        self.x = x

(我知道super()没有参数,但我不喜欢inconsistentmagic。)

在详细了解super 和 Python 中的 MRO 之后,我发现这段代码并不好。例如,子类化C 会导致

>>> class D(C): pass
>>> d = D(1)
......
RecursionError: maximum recursion depth exceeded while calling a Python object

我很害怕super() 没有从(词法还是动态?)上下文中神奇地提取其参数的参数,我决定添加一个“回调”来完成类定义,而不是使用它:

class C2:
    @classmethod
    def finalise(this_cls_yes_this_one):
        @staticmethod
        def __new__(cls, x=None):
            if isinstance(x, cls):
                return x
            else:
                return super(this_cls_yes_this_one, cls).__new__(cls)

        this_cls_yes_this_one.__new__ = __new__

        del this_cls_yes_this_one.finalise

    def __init__(self, x=None):
        if x is self: return
        self.x = x

C2.finalise()

【问题讨论】:

请考虑,对于可变值,这可能会产生令人惊讶的结果:a1.val=1; a2=A(a1); a2.val=2; print(a1.val); 您的类的用户可能期望构造函数为可变对象生成一个副本。 这实际上是预期的结果。正如我所说,我希望构造函数返回相同的对象。此外,我的类实例不适合突变。 【参考方案1】:

您可以使用元类。每当调用实例时,都会在 metacalss 的 __call__ 方法中调用 __init____new__ 方法。您可以通过覆盖元类__call__ 函数来处理这两种情况。

class MyMetaClass(type):

    def __call__(cls, obj, *args, **kwargs):
        if (isinstance(obj, cls)):
            return obj
        else:
            self = cls.__new__(cls, obj, *args, **kwargs)
            cls.__init__(self, obj, *args, **kwargs)
            return self
            # Note: In order to be sure that you don't miss anything
            # It's better to do super().__call__(obj, *args, **kwargs) here

class A(metaclass=MyMetaClass):
    def __init__(self, obj, *args, **kwargs):
        self.val = obj

演示:

a = A(10)
b = A(a)
c = A(40)
print(b is a)
print(a.val)
print(b.val)
print(c is a)
print(c.val)

# out:
True
10
10
False
40

【讨论】:

MyMetaClass.__call__else 子句可以是:else: return super().__call__(obj, *args, **kwargs) 吗? 是否有可能提供指向 Python 文档的链接以确认在 __cal__ 的类中没有其他需要注意的事项? @Alexey 我不确定,但 Rob 说您应该使用 super() 让您安心。 @Kasramvd - 抱歉,我没有仔细阅读这个问题。没关系。

以上是关于允许默认构造函数接受一个实例并原封不动地返回它的主要内容,如果未能解决你的问题,请参考以下文章

构造函数和原型

Typescript构造函数和继承

javascript构造函数小知识

TypeScript 类方法具有与构造函数相同的重载签名

构造函数与默认构造函数

C ++没有适当的默认构造函数我迷路了