2009-08-13 118 views
16

我總是通過該事實惱火:返回無或元組和拆包

$ cat foo.py 
def foo(flag): 
    if flag: 
     return (1,2) 
    else: 
     return None 

first, second = foo(True) 
first, second = foo(False) 

$ python foo.py 
Traceback (most recent call last): 
    File "foo.py", line 8, in <module> 
    first, second = foo(False) 
TypeError: 'NoneType' object is not iterable 

事實是,爲了不麻煩我要麼趕類型錯誤或有類似

正確解壓
values = foo(False) 
if values is not None: 
    first, second = values 

這是一種令人討厭的。有沒有一種技巧可以改善這種情況(例如,如果將第一個和第二個都設置爲無,而沒有返回(無,無))或者針對像我提交的那種情況的最佳設計策略的建議? *變量可能?

回答

16

嗯,你可以這樣做......

first,second = foo(True) or (None,None) 
first,second = foo(False) or (None,None) 

但據我知道有沒有擴大無填補元組的整體簡單的方法。

+0

ooooh ...這我沒有考慮。非常「完美」的方法,但聰明。 – 2009-08-13 22:11:20

+0

我不同意這是perl-ish。 'x或y'是說'(x?x:y)'或者'(如果x然後是x else y)'或者'(如果不是x然後是y)''或者其他想要解釋它的一種非常pythonic的方式。 – 2009-08-13 22:40:10

+0

好的,剛剛完成了「隨聲附和」的「無論或死()」典型的perl短語。 – 2009-08-13 23:52:38

7

我不認爲有一個竅門。您可以簡化調用代碼:

values = foo(False) 
if values: 
    first, second = values 

甚至:

values = foo(False) 
first, second = values or (first_default, second_default) 

其中first_default和second_default是價值你給第一和第二爲默認值。

+0

哪個python版本是第二個選項的工作? v =(1,); x,y = v或(v,2) – 2015-03-24 23:23:41

18

我不明白返回有什麼問題(無,無)。它比這裏提出的解決方案更清潔,它涉及到代碼中的更多更改。

它也沒有任何意義,你希望無自動分裂成2個變量。

+1

我只是在思考相同的過程。我同意它沒有意義:從概念的角度來看,返回None w.r.t. Nones的元組可能是不同的(當然這取決於實際的例程。在這裏,我們正在談論模擬案例) – 2009-08-13 22:18:28

+2

+1 - 絕對 - 讓函數返回如此混雜的結果並且必須在調用者中處理它,這很奇怪。 – 2009-08-13 22:19:50

+1

@亞歷克斯:好的,這取決於。一般來說,你可以有一些例程應該返回_x_(x = whatever),如果找不到則返回None。如果你知道x是一個元組,並且是一個包含兩個對象的元組,這是一個特殊情況,但是_x_或None如果未找到仍然有效。你打開包裝的事實對被調用的例程並不知情。 – 2009-08-13 22:24:25

2

您應該注意x or y的解決方案。他們工作,但他們比你的原始規格更廣泛一些。本質上,如果foo(True)返回一個空元組()呢?只要你知道把(None, None)當作是可以的,那麼你對提供的解決方案非常滿意。

如果這是一個常見的場景,我可能會寫一個實用功能,如:

# needs a better name! :) 
def to_tup(t): 
    return t if t is not None else (None, None) 

first, second = to_tup(foo(True)) 
first, second = to_tup(foo(False)) 
1
def foo(flag): 
    return ((1,2) if flag else (None, None)) 
14

我覺得這是抽象的問題。

函數應該保持某種抽象級別,這有助於降低代碼的複雜性。
在這種情況下,要麼是函數沒有維護正確的抽象,要麼是調用者不尊重它。

該功能可能是類似get_point2d();在這種情況下,抽象級別位於元組上,因此返回None將是發送某些特定情況(例如不存在的實體)的好方法。在這種情況下的錯誤將是期望兩個項目,而實際上唯一你知道的是該函數返回一個對象(有關2d點的信息)。

但它也可能是像get_two_values_from_db();在這種情況下,抽象會被返回None來打破,因爲函數(如名字所示)應該返回兩個值而不是一個

無論採用哪種方式,使用函數的主要目標 - 降低複雜度 - 至少部分會丟失。

請注意,此問題不會與原始名稱清晰顯示;這也是爲什麼給函數和方法提供好名字總是很重要的原因。

+1

偉大的評論。希望我能做到+2 – 2009-08-14 00:05:03

2

如何:

$ cat foo.py 
def foo(flag): 
    if flag: 
     return (1,2) 
    else: 
     return (None,)*2 

first, second = foo(True) 
first, second = foo(False) 

編輯:只要是明確的,唯一的變化是與return (None,)*2更換return None。我非常驚訝,沒有人想到這一點。 (或者如果他們有,我想知道他們爲什麼不使用它。)

+3

因爲OP明確表示「沒有foo返回(無,無)」。 'return(None,)* 2'與'return(None,None)'相同。 – mhawke 2009-08-14 01:47:08

+0

你確定這是什麼意思?我以爲OP只是想避免*長時間輸入* return None,None'。我的方式解決了這個問題。 – 2009-08-14 02:19:28

+0

嗯,它當然不會完全解決它,但確實會減少它,並且對於給定大小的更長的元組效果會更好。 – 2009-08-14 02:26:07

1

好的,我只會返回(None,None),但只要我們在whacko-land(heh)中,這裏是使用元組的子類的一種方法。在其他情況下,您不返回None,而是返回一個空容器,這似乎符合事物的精神。容器的「迭代器」在空白時解包無值。演示迭代器協議反正...

使用V2.5.2測試:

class Tuple(tuple): 
    def __iter__(self): 
     if self: 
      # If Tuple has contents, return normal tuple iterator... 
      return super(Tuple, self).__iter__() 
     else: 
      # Else return a bogus iterator that returns None twice... 
      class Nonerizer(object): 
       def __init__(self): 
        self.x=0 
       def __iter__(self): 
        return self 
       def next(self): 
        if self.x < 2: 
         self.x += 1 
         return None 
        else: 
         raise StopIteration 
      return Nonerizer() 


def foo(flag): 
    if flag: 
     return Tuple((1,2)) 
    else: 
     return Tuple() # It's not None, but it's an empty container. 

first, second = foo(True) 
print first, second 
first, second = foo(False) 
print first, second 

輸出是期望的:

1 2 
None None 
+0

好評代碼fu :) – 2009-08-14 02:09:21

+0

'Nonerizer()'可以替換爲'itertools.repeat(None,2)'。 – augurar 2015-02-17 19:10:55

+1

@augurar或只是'iter((None,None))',使整個練習毫無意義。 – 2017-08-08 17:45:22

0

我發現這個問題的解決方案:

返回無或返回一個對象。

但是你不想爲了返回一個對象而編寫一個類。對於這一點,你可以使用一個命名元組

像這樣:

from collections import namedtuple 
def foo(flag): 
if flag: 
    return None 
else: 
    MyResult = namedtuple('MyResult',['a','b','c'] 
    return MyResult._make([1,2,3]) 

然後:

result = foo(True) # result = True 
result = foo(False) # result = MyResult(a=1, b=2, c=3) 

而且你必須訪問的結果是這樣的:

print result.a # 1 
print result.b # 2 
print result.C# 3