2014-08-28 51 views
2

在我的項目中,我們有一個基於set的類。它可以從字符串或字符串的迭代(例如元組)或其他自定義類中初始化。當用迭代器初始化時,它將每個項目轉換爲特定的自定義類,如果它不是已經有的話。避免昂貴的__init__是使用__new__的一個很好的理由嗎?

因爲它可以從各種數據結構初始化,所以對這個類進行操作的很多方法(如__and__)在他們接受的內容中都是自由的,只是將它們的參數轉換爲這個類(即初始化一個新的實例)。當參數已經是類的一個實例並且有很多成員(它遍歷它們並檢查它們是正確的類型)時,我們發現這很慢。

我在想,爲了避免這種情況,我們可以在類中添加一個__new__方法,並且只要傳入的參數已經是類的一個實例,就直接返回它。這是否合理使用__new__

+4

如何修改'__and__'來檢查'isinstance(obj,CustomClass)'?並在不調用'CustomClass(obj)'的情況下處理這個常見的情況? – unutbu 2014-08-28 08:15:37

+0

@unutbu是的,這是第一個想法,但事實證明,有很多地方的CustomClass只是初始化沒有檢查。儘管如此,這可能是正確的答案,爲大多數這些地方添加「isinstance」檢查。 – pfctdayelise 2014-08-29 00:47:06

回答

3

添加 a __new__方法不會解決您的問題。從文檔__new__

如果__new__()回報cls一個實例,則新實例的 __init__()方法將被調用__init__(self[, ...]), 其中self是新實例,其餘參數是 相同如同傳遞給__new__()

。換句話說,返回相同的實例將防止蟒蛇與調用__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 s運行只有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代替鴨打字。

+0

太好了,非常感謝。我沒有仔細閱讀文檔。你爲什麼說「如果它是可變的,你要求隱藏的錯誤」? – pfctdayelise 2014-08-29 00:49:02

+0

另外這是一個非常有趣的關於集合的觀點,我從來沒有意識到以前的微妙差異,但它是有意義的,並且是API設計的一個好點。 – pfctdayelise 2014-08-29 00:50:05

+1

@pfctdayelise我的意思是,如果你正在處理可變數據,用戶會期望構造函數複製它。例如。 't = [1,2,3]; s = list(t); s.append(4) - > t也會被修改... WTF!?!'。但是,如果數據是不可變的(比如'tuple's),這不是一個問題,因爲用戶不能以一種意想不到地改變其他對象的方式對數據進行操作。 – Bakuriu 2014-08-29 06:53:00

相關問題