Allow the deafult constructor to accept an instance and return it unchainged

情到浓时终转凉″ 提交于 2019-12-11 04:34:51

问题


I would like to allow my class constructor to accept an instance of this class and in that case to return this same instance instead of creating a new object. Like what tuple does:

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

I imagine I need to override the __new__ method for this, and additionally take care of this special case in the __init__ method. Are there any recipes for this?

I would have preferred to completely skip __init__ when the constructor is given a class instance that it is going to return unchanged, but I see no way. I am also a bit suspicious about the triple use of cls in the line with super:

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

(I know about super() without arguments, but I do not like inconsistent magic.)

After learning more about super and about MRO in Python, i've figure out that this code is not good. For example, subclassing C results in

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

I am so terrified by super() without arguments that magically extracts its arguments from the (lexical or dynamic?) context, that rather than using it I've decided to add a "callback" to finalise the class definition:

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()

回答1:


You can use a metaclass. The __init__ and __new__ methods are called in __call__ method of the metacalss whenever an instance is call. You can handle both by overriding the metaclasses __call__ function.

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

Demo:

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


来源:https://stackoverflow.com/questions/48951677/allow-the-deafult-constructor-to-accept-an-instance-and-return-it-unchainged

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!