允许默认构造函数接受一个实例并原封不动地返回它
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__
,当构造函数被赋予一个类实例时,它将原样返回,但我看不到任何办法。对于cls
与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
(我知道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 - 抱歉,我没有仔细阅读这个问题。没关系。以上是关于允许默认构造函数接受一个实例并原封不动地返回它的主要内容,如果未能解决你的问题,请参考以下文章