在我的项目中,我们有一个基于set的类。它可以从字符串或字符串的可迭代(例如元组)或其他自定义类初始化。当使用iterable初始化时,它会将每个项目转换为特定的自定义类(如果它不是已经存在的类别)。
因为它可以从各种数据结构初始化,许多在这个类上运行的方法(例如__and__
)在它们接受的内容中是自由的,只是将它们的参数转换为这个类(即初始化)一个新的实例)。当参数已经是类的一个实例,并且有很多成员时(我们正在迭代它们并检查它们是正确的类型),我们发现这是相当慢的。
我想要避免这种情况,我们可以在类中添加__new__
方法,并且只要传入的参数已经是类的实例,就直接返回它。这是合理使用__new__
吗?
答案 0 :(得分:3)
添加 __new__
方法无法解决您的问题。来自__new__
的文档:
如果
__new__()
返回cls
的实例,则返回新实例 将调用__init__()
方法,如__init__(self[, ...])
, 其中self
是新实例,其余参数是 与传递给__new__()
的内容相同。
换句话说,返回相同的实例将不阻止python调用__init__
。
您可以非常轻松地验证这一点:
In [20]: class A:
...: def __new__(cls, arg):
...: if isinstance(arg, cls):
...: print('here')
...: return arg
...: return super().__new__(cls)
...: def __init__(self, values):
...: self.values = list(values)
In [21]: a = A([1,2,3])
In [22]: A(a)
here
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-22-c206e38274e0> in <module>()
----> 1 A(a)
<ipython-input-20-5a7322f37287> in __init__(self, values)
6 return super().__new__(cls)
7 def __init__(self, values):
----> 8 self.values = list(values)
TypeError: 'A' object is not iterable
如果不实现__init__
,只有__new__
, 可以能够完成这项工作。我相信这是tuple
所做的。
只有当你的类是不可变的时(例如tuple
这样做),这种行为才是可接受的,因为结果是明智的。如果它是可变的,你就会要求隐藏的错误。
更明智的方法是执行set
所做的事情:__*__
操作仅在set
上运行 ,但set
也提供了命名适用于任何可迭代的方法:
In [30]: set([1,2,3]) & [1,2]
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-30-dfd866b6c99b> in <module>()
----> 1 set([1,2,3]) & [1,2]
TypeError: unsupported operand type(s) for &: 'set' and 'list'
In [31]: set([1,2,3]) & set([1,2])
Out[31]: {1, 2}
In [32]: set([1,2,3]).intersection([1,2])
Out[32]: {1, 2}
通过这种方式,用户可以在API的速度和灵活性之间进行选择。
更简单的方法是unutbu提出的方法:在实现操作时使用isinstance
而不是duck-typing。