2009-08-13 22 views
16

Tôi luôn khó chịu bởi thực tế này:Trở Không có hoặc một tuple và giải nén

$ 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 

Thực tế là để giải nén một cách chính xác mà không rắc rối Tôi có một trong hai để bắt TypeError hoặc có cái gì đó như

values = foo(False) 
if values is not None: 
    first, second = values 

Đó là loại gây phiền nhiễu. Có cách nào để cải thiện tình huống này (ví dụ: để đặt cả từ đầu tiên và thứ hai thành Không có mà không có foo quay lại (Không, Không)) hoặc đề xuất về chiến lược thiết kế tốt nhất cho các trường hợp như tôi hiện diện? * biến có thể?

Trả lời

16

Vâng, bạn có thể làm ...

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

nhưng như xa như tôi biết không có cách nào đơn giản hơn để mở rộng Không để điền vào toàn bộ một tuple.

+0

ooooh ... điều này tôi đã không xem xét. cách tiếp cận rất "perlish", nhưng thông minh. –

+0

Tôi không đồng ý về việc đó là perl-ish. 'x hoặc y' là một cách rất kỳ quặc khi nói' (x? x: y) 'hoặc' (nếu x sau đó x khác y) 'hoặc' (nếu không x rồi y) 'hoặc cách khác bạn muốn giải thích nó . –

+0

ok, chỉ cần làm việc trong một "hát theo" các cụm từ perl điển hình "bất cứ điều gì hoặc chết()". –

7

Tôi không nghĩ rằng có một mẹo. Bạn có thể đơn giản hóa mã gọi đến:

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

hoặc thậm chí:

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

nơi first_default và second_default là những giá trị bạn muốn cung cấp cho đầu tiên và thứ hai là giá trị mặc định.

+0

Phiên bản python nào là tùy chọn thứ hai hoạt động? v = (1,); x, y = v hoặc (v, 2) –

18

Tôi không thấy có gì sai khi trả lại (Không, Không). Nó sạch hơn nhiều so với các giải pháp được đề xuất ở đây, trong đó có nhiều thay đổi hơn trong mã của bạn.

Cũng không có nghĩa là bạn muốn Không tự động chia thành 2 biến.

+1

Tôi chỉ đang trong quá trình suy nghĩ giống nhau. Tôi đồng ý rằng nó không có ý nghĩa: từ quan điểm khái niệm, trả về Không có w.r.t. một bộ Nones có lẽ khác nhau (tất nhiên nó phụ thuộc vào thói quen thực sự.Ở đây chúng tôi đang nói các trường hợp giả) –

+2

+1 - hoàn toàn - thật kỳ lạ khi làm cho hàm trả về kết quả hỗn hợp whacko như vậy và phải đối phó với nó trong người gọi. –

+1

@ Alex: tốt, điều đó phụ thuộc. Nói chung, bạn có thể có các trường hợp của các thói quen có nghĩa vụ trả về _x_ (với x = bất kỳ) hoặc None nếu không tìm thấy. Nếu bạn biết rằng x là một bộ tuple và một bộ gồm hai đối tượng, đó là trường hợp đặc biệt, nhưng trường hợp _x_ hoặc None nếu không tìm thấy vẫn hợp lệ. Thực tế là bạn giải nén nó là không biết đến thói quen được gọi là. –

2

Bạn nên cẩn thận với kiểu dáng x or y của giải pháp. Chúng hoạt động, nhưng chúng rộng hơn một chút so với đặc tả ban đầu của bạn. Về cơ bản, điều gì xảy ra nếu foo(True) trả về một bộ trống rỗng ()? Miễn là bạn biết rằng nó là OK để điều trị đó là (None, None), bạn tốt với các giải pháp được cung cấp.

Nếu đây là một kịch bản phổ biến, tôi có thể viết một hàm tiện ích như:

# 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

Tôi nghĩ có một vấn đề của trừu tượng.

Một chức năng nên duy trì một mức độ trừu tượng, giúp giảm độ phức tạp của mã.
Trong trường hợp này, hoặc chức năng không duy trì sự trừu tượng đúng, hoặc người gọi không tôn trọng nó.

Chức năng có thể là một cái gì đó như get_point2d(); trong trường hợp này, mức trừu tượng là trên tuple, và do đó trả về None sẽ là một cách tốt để báo hiệu một số trường hợp cụ thể (ví dụ như thực thể không tồn tại). Lỗi trong trường hợp này sẽ là mong đợi hai mục, trong khi thực sự điều duy nhất bạn biết là hàm trả về một đối tượng (với thông tin liên quan đến một điểm 2d).

Nhưng nó cũng có thể giống như get_two_values_from_db(); trong trường hợp này, trừu tượng sẽ bị phá vỡ bằng cách trả về Không, bởi vì hàm (như tên đề nghị) phải trả về hai giá trị chứ không phải một số!

Dù bằng cách nào, mục tiêu chính của việc sử dụng chức năng - giảm độ phức tạp - ít nhất là một phần, bị mất.

Lưu ý rằng vấn đề này sẽ không xuất hiện rõ ràng với tên gốc; đó cũng là lý do tại sao việc đặt tên tốt cho chức năng và phương pháp luôn là điều quan trọng.

+1

nhận xét tuyệt vời. Muốn tôi có thể làm +2 –

2

Làm thế nào về điều này:

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

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

Edit: Chỉ cần được rõ ràng, sự thay đổi duy nhất là để thay thế return None với return (None,)*2. Tôi vô cùng ngạc nhiên khi không ai khác nghĩ về điều này. (Hoặc nếu họ có, tôi muốn biết tại sao họ không sử dụng nó.)

+3

Bởi vì OP đã nói rõ ràng "mà không có foo trở về (Không, Không có gì)". 'return (None,) * 2' giống với' return (None, None) '. – mhawke

+0

Bạn có chắc đó là ý nghĩa của nó không? Tôi nghĩ OP chỉ muốn tránh * gõ * biểu thức dài 'trả về Không, Không có gì cả. Cách của tôi giải quyết vấn đề đó. –

+0

Vâng, nó không giải quyết nó hoàn toàn tất nhiên, nhưng làm giảm nó, và làm việc tốt hơn bao giờ hết cho tuples dài hơn của một kích thước nhất định. –

1

OK, tôi sẽ trở lại (Không, Không), nhưng miễn là chúng tôi đang ở trong đất hoang dã (heh), đây là một cách sử dụng một phân lớp của tuple. Trong trường hợp khác, bạn không trả lại Không, nhưng thay vào đó trả về một thùng chứa trống, có vẻ như là tinh thần của mọi thứ. Các gói "iterator" của vùng chứa Không có giá trị nào khi rỗng. Thể hiện sự giao thức iterator anyway ...

Tested sử dụng 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 

Output là mong muốn:

1 2 
None None 
+0

ovation đứng cho code-fu tốt đẹp :) –

+0

'Nonerizer()' có thể được thay thế bằng 'itertools.repeat (None, 2)'. – augurar

+1

@augurar hoặc chỉ 'lặp ((Không, Không))', làm cho toàn bộ bài tập vô nghĩa. –

0

Tôi tìm thấy một giải pháp cho vấn đề này:

Return Không có hoặc trả về một đối tượng.

Tuy nhiên bạn không muốn phải viết một lớp chỉ để trả lại một đối tượng. Đối với điều này, bạn có thể sử dụng một tên tuple

Như thế này:

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

Và sau đó:

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

Và bạn có quyền truy cập vào các kết quả như thế này:

print result.a # 1 
print result.b # 2 
print result.C# 3 
Các vấn đề liên quan