2009-09-22 63 views
7

(標題和閱讀Alex的答案後更新的內容)函數返回一個可迭代或不可迭代的函數取決於它的輸入是Pythonic嗎?

總的來說,我認爲,它被認爲是不好的形式(非Python化)的功能,有時會返回一個迭代,有時根據其參數的單個項目。

例如,struct.unpack總是返回一個元組,即使它只包含一個項目。

我試圖完成一個模塊的API和我有,可以採取一個或多個參數(通過*args)的一些功能是這樣的:

a = s.read(10)  # reads 10 bits and returns a single item 
b, c = s.read(5, 5) # reads 5 bits twice and returns a list of two items. 

所以它返回一個單一的項目,如果有隻有一個參數,否則返回一個列表。現在我認爲這很好,一點也不令人困惑,但我懷疑其他人可能會不同意。

最常見的用例,這些功能將只想要一個單一的項目回來,所以總是返回一個列表(或元組)覺得不妥:

a, = s.read(10)  # Prone to bugs when people forget to unpack the object 
a = s.read(10)[0]  # Ugly and it's not clear only one item is being returned 

另一種選擇是有兩個功能:

a = s.read(10) 
b, c = s.read_list(5, 5) 

這是好的,但它使API變得混亂,並且要求用戶記住兩倍的功能而不增加任何值。

所以我的問題是:有時會返回一個迭代,有時會單個項目混淆和un-Pythonic?如果是這樣,最好的選擇是什麼?


更新:我覺得一般的共識是,這是很調皮只返回一個可迭代的時候。我認爲大多數情況下最好的選擇是總是返回迭代,即使它只包含一個項目。

話雖如此,對於我的具體情況,我想我會去分裂成兩個函數(read(item)/readlist(*items)),推理是,我認爲,單個項目的情況下會更經常發生比多個項目案例,所以它使得它更易於使用,並且API更改對用戶來說不會有問題。

謝謝大家。

回答

12

如果你將要返回有時迭代器,以及對他人的單個對象,我想說的回報總是一個迭代器,所以你不必想想看。一般來說,你會在需要迭代器的上下文中使用該函數,所以如果你必須檢查它是在哪裏迭代一個列表或者一個對象只做一次工作,那麼它更容易返回一個迭代器並且總是迭代,即使它是一次。

如果你需要做一些不同的事情,如果你返回一個元素,只需使用if len(var):

請記住,一致性是一個寶貴的好處。

我傾向於返回一致的對象,而不是相同的類型,但如果我返回一個可迭代對象,我總是返回一個迭代對象。

+2

+1。有時成爲事物,有時候成爲事物清單通常是一個錯誤。 Python爲%格式化做了這個,這被廣泛認爲是一個錯誤和令人討厭的陷阱。 – bobince 2009-09-22 17:32:46

+0

我很害怕人們會這樣說 - 當你清楚地只詢問一件物品時,只是感覺很難得到一份清單! – 2009-09-22 18:27:48

+0

@Scot Griffiths:恕我直言,潛在的錯誤是由於過於聰明而導致簡單變量可能導致的問題。爲什麼不使用像'def read(a_tuple):'而不是使用'* args'的方法? – voyager 2009-09-22 19:40:45

0

在Python列表是對象:)所以沒有類型不匹配

+0

夠正確!我編輯了這個問題以避免混淆。 – 2009-09-22 17:27:16

1

唯一的情況是,我會這樣做的一個參數化函數或方法,調用者給出的一個或多個參數決定返回的類型;例如,一個「工廠」函數返回一個邏輯上類似於家庭對象之一:

newCharacter = characterFactory("human", "male", "warrior") 

在一般情況下,如果調用者沒有獲得指定,我會避免「一盒巧克力「行爲。 :)

+0

在我的特殊情況下,返回的項目數量等於函數調用中給出的項目數量,所以我不認爲用戶會對返回的內容感到驚訝。 – 2009-09-22 18:18:43

2

一般來說,我不得不說,返回兩種不同的類型是不好的做法。

想象一下下一位開發人員來閱讀和維護您的代碼。起初他/她會使用你的函數來閱讀一個方法,並且認爲「啊,read()返回一個單一的項目。」

後來他們會看到代碼將read()的結果視爲列表。這最多隻會混淆它們,迫使它們檢查read()的用法。在最壞的情況下,他們可能會認爲在使用read()的實現中存在一個錯誤並嘗試修復它。

最後,一旦他們明白read()返回兩個可能的類型,他們將不得不問自己「是否有可能需要第三個返回類型?

這讓我想起了這樣一句話:「代碼就好像下一個維護你的代碼的人是一個知道你住在哪裏的殺人狂。」

1

它可能不是「pythonic」的問題,而是「好設計」的問題。如果你返回不同的東西沒有人需要對他們進行typechecks,那麼它可能是好的。這是你的多態性。 OTOH,如果調用者必須「穿透面紗」,那麼你有一個設計問題,被稱爲違反Liskov替代原則。 Pythonic與否,顯然不是OO設計,這意味着它容易出現錯誤和編程不便。

1

我會讀(整數)和read_list(可迭代)。

通過這種方式,您可以讀取(10)並獲取單個結果和read_list([5,5,10,5])並獲取結果列表。這更加靈活和明確。

2

根據參數返回單個對象或對象的迭代,肯定很難處理。但是,標題中的問題更加籠統,標準庫函數避免(或「大部分避免」)基於參數返回不同類型的說法是非常不正確的。有很多反例。

函數copy.copycopy.deepcopy返回與它們的參數相同的類型,所以當然它們「根據參數返回不同的類型」。 「返回與輸入相同的類型」實際上非常常見 - 您可以在這裏上課,也可以從「放置對象的容器中取回對象」,儘管通常使用方法而不是函數完成;-) 。而且,在同樣,考慮itertools.repeat(一旦你迭代其返回的迭代器),或者說,filter ...:

>>> filter(lambda x: x>'f', 'zaplepidop') 
'zplpiop' 
>>> filter(lambda x: x>'f', list('zaplepidop')) 
['z', 'p', 'l', 'p', 'i', 'o', 'p'] 

過濾字符串返回一個字符串,過濾一個列表返回一個列表。

別急,還有更重要的 - - !)功能pickle.loads和它的朋友(例如,在模塊的類型完全依賴於你傳遞作爲參數值marshal & c)返回的對象。內置函數eval(以及類似的input,在Python 2. *中)也是如此。這是第二種常見模式:根據參數的值,廣泛的(甚至是無限的)各種可能類型構造或重建一個對象,並返回它。

我不知道你觀察到的具體反模式的好例子(我確實相信這是一種反模式,輕度 - 不是因爲任何高福利因素,只是因爲它討厭和不方便處理與;-)。請注意,我已經舉例說明了這些情況,方便又方便 - 這是大多數標準庫問題中的真正設計判別式! - )

+0

你說得對,這個問題的表達方式太籠統了,它確實歸結爲只是一個可重複的問題與不可解決的問題。我想如果你稱它爲反模式,那麼這就是它的死亡之錘!) – 2009-09-23 13:01:48