Trả lại, trả lại Không, và không trả lại chút nào?


386

Hãy xem xét ba chức năng:

def my_func1():
  print "Hello World"
  return None

def my_func2():
  print "Hello World"
  return

def my_func3():
  print "Hello World"

Tất cả đều xuất hiện để trả về Không. Có sự khác biệt nào giữa giá trị trả về của các hàm này không? Có bất kỳ lý do để thích cái này hơn cái kia không?


40
Lưu ý rằng có một sự khác biệt về phong cách. return Nonengụ ý với tôi rằng hàm đôi khi có Nonegiá trị không trả về, nhưng tại vị trí của return None, không có giá trị trả về như vậy. Viết không hoàn returntoàn ngụ ý với tôi không bao giờ có giá trị trả về thú vị, giống như một "thủ tục" trái ngược với "chức năng". returnngụ ý tồn tại sớm từ một "thủ tục" theo điểm trước đó.

Câu trả lời:


500

Về hành vi thực tế, không có sự khác biệt. Tất cả họ trở vềNone và đó là nó. Tuy nhiên, có một thời gian và địa điểm cho tất cả những điều này. Các hướng dẫn sau đây về cơ bản là cách sử dụng các phương pháp khác nhau (hoặc ít nhất là cách tôi được dạy chúng nên được sử dụng), nhưng chúng không phải là quy tắc tuyệt đối để bạn có thể kết hợp chúng nếu bạn cảm thấy cần thiết.

Sử dụng return None

Điều này nói rằng hàm thực sự có nghĩa là trả về một giá trị để sử dụng sau này và trong trường hợp này nó trả về None. Giá trị này Nonesau đó có thể được sử dụng ở nơi khác. return Nonekhông bao giờ được sử dụng nếu không có giá trị trả về nào khác từ hàm.

Trong ví dụ sau, chúng tôi trở lại personmothernếu personnhất định là một con người. Nếu đó không phải là con người, chúng tôi sẽ quay trở lại Nonepersonkhông có mother(giả sử đó không phải là động vật hay thứ gì đó).

def get_mother(person):
    if is_human(person):
        return person.mother
    else:
        return None

Sử dụng return

Điều này được sử dụng cho cùng một lý do như breaktrong các vòng lặp. Giá trị trả về không quan trọng và bạn chỉ muốn thoát toàn bộ hàm. Nó cực kỳ hữu ích ở một số nơi, mặc dù bạn không cần nó thường xuyên.

Chúng tôi đã có 15 prisonersvà chúng tôi biết một trong số họ có một con dao. Chúng tôi lặp lại từng prisonercái một để kiểm tra xem chúng có dao không. Nếu chúng ta đâm vào người bằng dao, chúng ta có thể thoát khỏi chức năng vì chúng ta biết chỉ có một con dao và không có lý do gì để kiểm tra phần còn lại của prisoners. Nếu chúng ta không tìm thấy prisonercon dao, chúng ta sẽ cảnh giác. Điều này có thể được thực hiện theo nhiều cách khác nhau và sử dụng returncó lẽ không phải là cách tốt nhất, nhưng nó chỉ là một ví dụ để chỉ ra cách sử dụng returnđể thoát khỏi một chức năng.

def find_prisoner_with_knife(prisoners):
    for prisoner in prisoners:
        if "knife" in prisoner.items:
            prisoner.move_to_inquisition()
            return # no need to check rest of the prisoners nor raise an alert
    raise_alert()

Lưu ý: Bạn không bao giờ nên làm var = find_prisoner_with_knife(), vì giá trị trả về không có nghĩa là bị bắt.

Sử dụng không có returnở tất cả các

Điều này cũng sẽ trở lại None, nhưng giá trị đó không có nghĩa là được sử dụng hoặc bắt. Nó đơn giản có nghĩa là chức năng đã kết thúc thành công. Về cơ bản, nó giống như returntrong các voidhàm trong các ngôn ngữ như C ++ hoặc Java.

Trong ví dụ sau, chúng tôi đặt tên mẹ của người đó và sau đó hàm sẽ thoát sau khi hoàn thành thành công.

def set_mother(person, mother):
    if is_human(person):
        person.mother = mother

Lưu ý: Bạn không bao giờ nên làm var = set_mother(my_person, my_mother), vì giá trị trả về không có nghĩa là bị bắt.


5
var = get_mother()varNone
Sẽ

Làm thế nào một người nên chấp nhận giá trị trả lại nếu var = get_mother()không thể được sử dụng? IMHO hoàn toàn ổn để làm điều đó. Bạn chỉ cần đảm bảo kiểm tra Nonegiá trị không if vartrước khi sử dụng.
winklerrr

Tôi nghĩ điều quan trọng là phải đề cập rằng đây không chỉ là một quy ước tốt, mà PEP8 yêu cầu. Tôi đăng một câu trả lời để khen ngợi bạn và trích dẫn PEP8 về điều này.
Fabiano

29

Vâng, tất cả đều giống nhau.

Chúng tôi có thể xem lại mã máy đã được giải thích để xác nhận rằng tất cả chúng đều thực hiện cùng một thứ.

import dis

def f1():
  print "Hello World"
  return None

def f2():
  print "Hello World"
  return

def f3():
  print "Hello World"

dis.dis(f1)
    4   0 LOAD_CONST    1 ('Hello World')
        3 PRINT_ITEM
        4 PRINT_NEWLINE

    5   5 LOAD_CONST    0 (None)
        8 RETURN_VALUE

dis.dis(f2)
    9   0 LOAD_CONST    1 ('Hello World')
        3 PRINT_ITEM
        4 PRINT_NEWLINE

    10  5 LOAD_CONST    0 (None)
        8 RETURN_VALUE

dis.dis(f3)
    14  0 LOAD_CONST    1 ('Hello World')
        3 PRINT_ITEM
        4 PRINT_NEWLINE            
        5 LOAD_CONST    0 (None)
        8 RETURN_VALUE      

4
cẩn thận ở đây ... dis.distrả về None: -X
mgilson

Cách duy nhất để có được đầu ra của dis dưới dạng chuỗi là chuyển hướng stdout sang một loại bộ đệm IO (ví dụ StringIO). Ngay cả khi đó, so sánh trực tiếp như thế có thể không hoạt động vì tôi tin rằng dis.discũng báo cáo một số số dòng ...
mgilson

Dù sao họ cũng không sử dụng cùng một thanh ghi chính xác hay không, vì vậy tôi đã thay đổi mã của mình để bây giờ chúng ta chỉ nhìn vào mã máy thay vì thực hiện một số so sánh chuỗi ngớ ngẩn. Cảm ơn cho những cái đầu rất lớn lên.
David Marx

19

Mỗi cái đều trả về cùng một singleton None- Không có sự khác biệt về chức năng.

Tôi nghĩ rằng việc bỏ qua returncâu lệnh là hợp lý, trừ khi bạn cần nó thoát ra khỏi chức năng sớm (trong trường hợp trần returnthì phổ biến hơn), hoặc trả lại một cái gì đó khác None. Nó cũng có ý nghĩa và dường như là thành ngữ để viết return Nonekhi nó ở trong một hàm có một đường dẫn khác trả về một cái gì đó khác hơn None. Viết return Nonera một cách rõ ràng là một gợi ý trực quan cho người đọc rằng có một nhánh khác trả về một thứ gì đó thú vị hơn (và mã gọi đó có thể sẽ cần xử lý cả hai loại giá trị trả về).

Thông thường trong Python, các hàm trả về Noneđược sử dụng như các voidhàm trong C - Mục đích của chúng thường là hoạt động trên các đối số đầu vào tại chỗ (trừ khi bạn đang sử dụng dữ liệu toàn cầu ( shudders )). Trở về Nonethường làm cho rõ ràng hơn rằng các đối số đã bị đột biến. Điều này làm cho nó rõ ràng hơn một chút tại sao nó có ý nghĩa để rời khỏireturn tuyên bố từ quan điểm "quy ước ngôn ngữ".

Điều đó nói rằng, nếu bạn đang làm việc trong một cơ sở mã đã có các quy ước được thiết lập sẵn xung quanh những điều này, tôi chắc chắn sẽ làm theo để giúp cơ sở mã được thống nhất ...


1
Hay nói chung hơn, bất cứ khi nào một chức năng kết thúc mà không nhấn một returncâu lệnh, nó sẽ trả về None.

Nó không phải là thành ngữ để bỏ qua câu lệnh return nếu hàm được mong đợi trả về một giá trị. Chỉ các chức năng chỉ hoạt động thông qua các tác dụng phụ nên bỏ qua câu lệnh return.
phân tử

@molecule - Vâng, tôi nghĩ rằng có lẽ tôi đã không làm công việc tốt nhất trong lời giải thích của tôi ở đây. Tôi đã cố gắng cập nhật nó mà không biến nó thành một bản sao hoàn chỉnh của câu trả lời (tốt hơn nhiều) ở trên. Tôi vẫn chưa hài lòng lắm với bài đăng này ... Một phần trong tôi muốn xóa nó (vì câu trả lời ở trên tốt hơn rất nhiều) và một phần trong tôi muốn giữ nó - 9 người cho đến nay đã nghĩ rằng nó tốt cho lý do này hay lý do khác Có lẽ tôi đánh vào thứ gì đó mà người khác bỏ lỡ?
mgilson

1
@ZAB - Tôi không nghĩ điều đó là có thể. Trong python tôi có thể khỉ vá bất cứ thứ gì với bất cứ thứ gì (đây là do thiết kế và đó là một tính năng tuyệt vời nếu được sử dụng một cách tiết kiệm). Cụ thể, tôi có thể vá lỗi một hàm có trả về với hàm không có. Tất cả các kiểm tra "không phải là biểu thức" xảy ra vào thời điểm phân tích, nhưng do vá khỉ, điều này không thể xảy ra cho đến khi chạy. IOW, nó hoàn toàn khác nhau. Cá nhân, tôi nghĩ rằng hành vi hiện tại khá hợp lý (và phù hợp với các ngôn ngữ cấp cao khác), nhưng tôi không phải là người bạn cần thuyết phục :-).
mgilson

1
@ZAB - cả hai RubyJavascriptcó cùng một cách tiếp cận như python. Tôi chắc chắn rằng có những ví dụ về các ngôn ngữ cấp cao nói "void" trong biểu thức nhưng tôi không thể nghĩ ra bất kỳ Javangôn ngữ nào vào lúc này (có thể nếu bạn xem xét một ngôn ngữ cấp cao) ... Làm thế nào để vá khỉ một chức năng (rất hữu ích để chế nhạo trong các bài kiểm tra trong số những thứ khác) hoạt động trong một thế giới giả thuyết, nơi trăn có một voidtừ khóa làm cho nó để chức năng không trả về bất cứ điều gì? (Ngoài ra, như tôi đã nói trước đây, bạn không cần phải thuyết phục tôi rằng đây là một thiết kế tồi. Tôi không thể làm bất cứ điều gì ngay cả khi bạn đúng)
mgilson

4

Như những người khác đã trả lời, kết quả hoàn toàn giống nhau, Noneđược trả về trong mọi trường hợp.

Sự khác biệt là phong cách, nhưng xin lưu ý rằng PEP8 yêu cầu sử dụng phải nhất quán:

Hãy nhất quán trong các tuyên bố trở lại. Tất cả các câu lệnh return trong một hàm sẽ trả về một biểu thức, hoặc không có câu lệnh nào trong số chúng nên. Nếu bất kỳ câu lệnh trả về nào trả về một biểu thức, thì bất kỳ câu lệnh trả lại nào không có giá trị nào được trả về đều phải nêu rõ đây là câu trả về Không và câu lệnh trả về rõ ràng sẽ xuất hiện ở cuối hàm (nếu có thể truy cập được).

Đúng:

def foo(x):
    if x >= 0:
        return math.sqrt(x)
    else:
        return None

def bar(x):
    if x < 0:
        return None
    return math.sqrt(x)

Không:

def foo(x):
    if x >= 0:
        return math.sqrt(x)

def bar(x):
    if x < 0:
        return
    return math.sqrt(x)

https://www.python.org/dev/peps/pep-0008/#programming-recommendations


Về cơ bản, nếu bạn từng trả về Nonegiá trị không trong một hàm, điều đó có nghĩa là giá trị trả về có ý nghĩa và có nghĩa là bị người gọi bắt. Vì vậy, khi bạn trở lại None, nó cũng phải rõ ràng, để truyền đạt Nonetrong trường hợp này có ý nghĩa, nó là một trong những giá trị trả về có thể.

Nếu bạn hoàn toàn không cần trả về, thì về cơ bản, chức năng của bạn hoạt động như một thủ tục thay vì chức năng, vì vậy chỉ cần không bao gồm returncâu lệnh.

Nếu bạn đang viết một hàm giống như thủ tục và có cơ hội quay lại sớm hơn (nghĩa là bạn đã hoàn thành vào thời điểm đó và không cần thực thi phần còn lại của hàm), bạn có thể sử dụng một returns trống để báo hiệu cho đầu đọc nó chỉ là sự kết thúc sớm của việc thực hiện và Nonegiá trị được trả về hoàn toàn không có ý nghĩa gì và không bị bắt (hàm giống như thủ tục luôn luôn trả về None).


3

Về mặt chức năng, tất cả đều giống nhau, sự khác biệt giữa chúng là về khả năng đọc mã và kiểu dáng (điều quan trọng cần xem xét)

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.