Một hàm trả về nhiều giá trị có phải là điều khó hiểu không?


81

Trong python, bạn có thể có một hàm trả về nhiều giá trị. Đây là một ví dụ giả định:

def divide(x, y):
    quotient = x/y
    remainder = x % y
    return quotient, remainder  

(q, r) = divide(22, 7)

Điều này có vẻ rất hữu ích, nhưng có vẻ như nó cũng có thể bị lạm dụng ("Chà..chức năng X đã tính những gì chúng ta cần làm giá trị trung gian. Hãy để X cũng trả về giá trị đó").

Khi nào bạn nên vẽ đường thẳng và xác định một phương pháp khác?

Câu trả lời:


107

Hoàn toàn có thể (đối với ví dụ bạn đã cung cấp).

Tuples là công dân hạng nhất trong Python

Có một chức năng nội trang divmod()thực hiện chính xác điều đó.

q, r = divmod(x, y) # ((x - x%y)/y, x%y) Invariant: div*y + mod == x

Có nhiều ví dụ khác: zip, enumerate, dict.items.

for i, e in enumerate([1, 3, 3]):
    print "index=%d, element=%s" % (i, e)

# reverse keys and values in a dictionary
d = dict((v, k) for k, v in adict.items()) # or 
d = dict(zip(adict.values(), adict.keys()))

BTW, dấu ngoặc đơn không cần thiết trong hầu hết thời gian. Trích dẫn từ Tham khảo Thư viện Python :

Tuples có thể được xây dựng theo một số cách:

  • Sử dụng một cặp dấu ngoặc đơn để biểu thị bộ giá trị trống: ()
  • Sử dụng dấu phẩy ở cuối cho một tuple đơn lẻ: a, hoặc (a,)
  • Ngăn cách các mục bằng dấu phẩy: a, b, c hoặc (a, b, c)
  • Sử dụng tuple () tích hợp sẵn: tuple () hoặc tuple (có thể lặp lại)

Các chức năng phải phục vụ một mục đích duy nhất

Do đó, họ sẽ trả về một đối tượng duy nhất. Trong trường hợp của bạn, đối tượng này là một bộ giá trị. Coi tuple là một cấu trúc dữ liệu phức hợp đặc biệt. Có những ngôn ngữ mà hầu hết mọi hàm đều trả về nhiều giá trị (danh sách trong Lisp).

Đôi khi nó là đủ để trả lại (x, y)thay vì Point(x, y).

Các bộ giá trị được đặt tên

Với sự ra đời của các bộ giá trị được đặt tên trong Python 2.6, trong nhiều trường hợp, việc trả về các bộ giá trị được đặt tên thay vì các bộ giá trị đơn giản là tốt hơn.

>>> import collections
>>> Point = collections.namedtuple('Point', 'x y')
>>> x, y = Point(0, 1)
>>> p = Point(x, y)
>>> x, y, p
(0, 1, Point(x=0, y=1))
>>> p.x, p.y, p[0], p[1]
(0, 1, 0, 1)
>>> for i in p:
...   print(i)
...
0
1

1
Khả năng sử dụng tuples như vậy là rất tiện dụng. Tôi thực sự nhớ có khả năng đó trong Java một số lần.
MBCook

3
Cảm ơn rất nhiều vì đã đề cập đến bộ giá trị được đặt tên. Tôi đã tìm kiếm một cái gì đó như thế này.
vobject 10/09/09

đối với các giá trị trả về hạn chế, dict () có thể đơn giản hơn / nhanh hơn (đặc biệt nếu hàm sau đó được sử dụng cho REST API và JSON). trên hệ thống của tôi, def test_nt (): Point = nametuple ('Point', ['x', 'y']) p = Point (10.5, 11.5) json.dumps (p._asdict ()) mất 819 μs / vòng lặp , def test_dict (): d = { 'x': 10,5, 'y': 11,5} json.dumps (d) có 15.9μs / vòng lặp .... vì vậy> 50x nhanh hơn
Comte

@comte: Vâng, Point(10.5, 11.5)chậm hơn 6 lần {'x': 10.5, 'y':11.5}. Thời gian tuyệt đối là 635 ns +- 26 nsso với105 ns +- 4 ns . Không có khả năng đó sẽ là một nút thắt cổ chai trong ứng dụng - đừng tối ưu hóa trừ khi hồ sơ của bạn nói khác. Đừng tạo các lớp ở cấp hàm trừ khi bạn biết lý do tại sao bạn cần nó. Nếu API của bạn yêu cầu dicthơn là sử dụng dict- nó không liên quan gì đến hiệu suất.
jfs

27

Trước tiên, hãy lưu ý rằng Python cho phép những điều sau (không cần dấu ngoặc đơn):

q, r = divide(22, 7)

Về câu hỏi của bạn, không có quy tắc nào khó và nhanh cả. Đối với các ví dụ đơn giản (và thường là giả thiết), có vẻ như luôn có thể xảy ra với một hàm đã cho chỉ có một mục đích, dẫn đến một giá trị duy nhất. Tuy nhiên, khi sử dụng Python cho các ứng dụng trong thế giới thực, bạn sẽ nhanh chóng gặp phải nhiều trường hợp trong đó việc trả về nhiều giá trị là cần thiết và dẫn đến mã sạch hơn.

Vì vậy, tôi muốn nói rằng hãy làm bất cứ điều gì có ý nghĩa và đừng cố gắng tuân theo một quy ước nhân tạo. Python hỗ trợ nhiều giá trị trả về, vì vậy hãy sử dụng nó khi thích hợp.


4
dấu phẩy tạo ra tuple, do đó biểu thức: q, r một tuple.
jfs 14/09/08

Vinko, một ví dụ là tempfile.mkstemp () từ thư viện tiêu chuẩn, trả về một tuple chứa một tệp xử lý và một đường dẫn tuyệt đối. JFS, vâng, bạn nói đúng.
Jason Etheridge

Nhưng đó là một mục đích duy nhất ... Bạn có thể có mục đích duy nhất và nhiều giá trị lợi nhuận
Vinko Vrsalovic

Nếu các ngôn ngữ khác có thể có nhiều kiểu trả về, tôi sẽ không bị mắc kẹt với các tham chiếu và tham số 'out'.
ban đầu

Trả về nhiều giá trị là một trong những tính năng tuyệt vời nhất trong python!
Martin Beckett

13

Ví dụ bạn đưa ra thực sự là một hàm nội trang của python, được gọi divmod. Vì vậy, ai đó, vào một thời điểm nào đó, nghĩ rằng nó đủ lớn để đưa vào chức năng cốt lõi.

Đối với tôi, nếu nó làm cho mã sạch hơn, nó là pythonic. So sánh hai khối mã này:

seconds = 1234
minutes, seconds = divmod(seconds, 60)
hours, minutes = divmod(minutes, 60)

seconds = 1234
minutes = seconds / 60
seconds = seconds % 60
hours = minutes / 60
minutes = minutes % 60

3

Có, trả về nhiều giá trị (tức là một bộ giá trị) chắc chắn là pythonic. Như những người khác đã chỉ ra, có rất nhiều ví dụ trong thư viện chuẩn Python, cũng như trong các dự án Python uy tín. Hai nhận xét bổ sung:

  1. Trả về nhiều giá trị đôi khi rất rất hữu ích. Lấy ví dụ, một phương thức tùy chọn xử lý một sự kiện (trả về một số giá trị khi làm như vậy) và cũng trả về thành công hoặc thất bại. Điều này có thể nảy sinh trong một chuỗi trách nhiệm. Trong các trường hợp khác, bạn muốn trả về nhiều phần dữ liệu được liên kết chặt chẽ --- như trong ví dụ đã cho. Trong cài đặt này, việc trả về nhiều giá trị cũng giống như việc trả về một trường hợp đơn lẻ của một lớp ẩn danh với một số biến thành viên.
  2. Việc xử lý các đối số phương thức của Python đòi hỏi khả năng trả về trực tiếp nhiều giá trị. Ví dụ, trong C ++, các đối số của phương thức có thể được truyền bằng tham chiếu, vì vậy bạn có thể gán giá trị đầu ra cho chúng, ngoài giá trị trả về chính thức. Trong Python, các đối số được truyền "bằng tham chiếu" (nhưng theo nghĩa của Java, không phải C ++). Bạn không thể gán giá trị mới cho các đối số của phương thức và nó được phản ánh bên ngoài phạm vi phương thức. Ví dụ:

    // C++
    void test(int& arg)
    {
        arg = 1;
    }
    
    int foo = 0;
    test(foo); // foo is now 1!
    

    So sánh với:

    # Python
    def test(arg):
        arg = 1
    
    foo = 0
    test(foo) # foo is still 0
    

"một phương thức tùy chọn xử lý một sự kiện và cũng trả về thành công hoặc thất bại" - Đó là những gì ngoại lệ dành cho.
Nathan

Rất nhiều người đã chấp nhận rằng các trường hợp ngoại lệ chỉ dành cho các điều kiện "đặc biệt", không chỉ cho sự thành công hay thất bại. Google "mã lỗi và ngoại lệ" cho một số cuộc thảo luận.
zweiterlinde

1

Nó chắc chắn là pythonic. Thực tế là bạn có thể trả về nhiều giá trị từ một hàm mà bảng soạn sẵn bạn sẽ có bằng một ngôn ngữ như C, nơi bạn cần xác định một cấu trúc cho mọi kết hợp kiểu mà bạn trả về ở đâu đó.

Tuy nhiên, nếu bạn đang trả về một thứ gì đó điên rồ như 10 giá trị từ một hàm duy nhất, bạn nên nghiêm túc xem xét việc gộp chúng vào một lớp vì lúc đó nó sẽ khó sử dụng.



1

OT: Algol68 của RSRE có toán tử "/: =" gây tò mò. ví dụ.

INT quotient:=355, remainder;
remainder := (quotient /:= 113);

Cho thương là 3 và dư là 16.

Lưu ý: thông thường giá trị của "(x /: = y)" bị loại bỏ vì thương "x" được gán bằng tham chiếu, nhưng trong trường hợp của RSRE, giá trị trả về là phần còn lại.

cf Số học Số nguyên - Algol68


0

Bạn có thể trả về nhiều giá trị bằng cách sử dụng một bộ tuple cho các chức năng đơn giản như divmod. Nếu nó làm cho mã có thể đọc được, thì đó là Pythonic.

Nếu giá trị trả về bắt đầu trở nên khó hiểu, hãy kiểm tra xem hàm có hoạt động quá nhiều hay không và chia nhỏ nó ra nếu có. Nếu một tuple lớn đang được sử dụng như một đối tượng, hãy biến nó thành một đối tượng. Ngoài ra, hãy cân nhắc sử dụng các bộ giá trị được đặt tên , sẽ là một phần của thư viện chuẩn trong Python 2.6.


0

Tôi khá mới với Python, nhưng kỹ thuật tuple có vẻ rất khó hiểu đối với tôi. Tuy nhiên, tôi đã có một ý tưởng khác có thể nâng cao khả năng đọc. Sử dụng từ điển cho phép truy cập vào các giá trị khác nhau theo tên thay vì vị trí. Ví dụ:

def divide(x, y):
    return {'quotient': x/y, 'remainder':x%y }

answer = divide(22, 7)
print answer['quotient']
print answer['remainder']

2
Đừng làm điều này. Bạn không thể gán kết quả trong một lớp lót, nó rất khó hiểu. (Trừ khi bạn muốn lưu trữ kết quả, trong trường hợp đó tốt hơn nên đặt nó vào một phương thức.) Nếu mục đích của bạn với dict là tự ghi lại các giá trị trả về, thì a) hãy đặt tên giải thích hợp lý cho fn (như " divmod ") và b) đặt phần còn lại của lời giải thích vào một chuỗi doc (là nơi Pythonic để đặt chúng); Nếu chuỗi doc đó yêu cầu dài hơn hai dòng, điều đó cho thấy hàm đang bị quá tải theo chủ đề và có thể là một ý tưởng tồi.
smci
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.