Cách Pythonic để tránh các câu lệnh x nếu return x x return


218

Tôi có một phương thức gọi 4 phương thức khác theo trình tự để kiểm tra các điều kiện cụ thể và trả về ngay lập tức (không kiểm tra các phương thức sau) mỗi khi trả về một điều gì đó Sự thật.

def check_all_conditions():
    x = check_size()
    if x:
        return x

    x = check_color()
    if x:
        return x

    x = check_tone()
    if x:
        return x

    x = check_flavor()
    if x:
        return x
    return None

Điều này có vẻ như rất nhiều mã hành lý. Thay vì mỗi câu lệnh if 2 dòng, tôi muốn làm một cái gì đó như:

x and return x

Nhưng đó là Python không hợp lệ. Tôi có thiếu một giải pháp đơn giản, thanh lịch ở đây không? Ngẫu nhiên, trong tình huống này, bốn phương thức kiểm tra đó có thể tốn kém, vì vậy tôi không muốn gọi chúng nhiều lần.


7
Những x này là gì? Chúng chỉ là Đúng / Sai, hay chúng là cấu trúc dữ liệu có chứa một số thông tin, không có hoặc tương tự được sử dụng như một trường hợp đặc biệt để chỉ ra sự vắng mặt của bất kỳ dữ liệu nào? Nếu đó là cái sau, bạn gần như chắc chắn sẽ sử dụng ngoại lệ thay thế.
Nathaniel

13
@gerrit Mã như được trình bày ở trên là mã giả định / mã giả không có chủ đề trên Đánh giá mã. Nếu tác giả của bài đăng muốn nhận được mã làm việc thực tế, thực tế của họ , thì có, họ được hoan nghênh đăng lên Đánh giá mã.
Phrancis

4
Tại sao bạn nghĩ x and return xlà tốt hơn if x: return x? Thứ hai là dễ đọc hơn và do đó có thể duy trì. Bạn không nên lo lắng quá nhiều về số lượng ký tự hoặc dòng; số lượng dễ đọc. Dù sao chúng cũng có cùng số lượng ký tự không phải khoảng trắng, và nếu bạn thực sự phải, if x: return xsẽ hoạt động tốt trên chỉ một dòng.
marcelm

3
Vui lòng làm rõ liệu bạn quan tâm đến các giá trị thực tế hay bạn thực sự chỉ cần trả về một boolean. Điều này tạo ra sự khác biệt về các tùy chọn có sẵn và cũng là lựa chọn nào truyền đạt rõ hơn ý định. Việc đặt tên cho thấy bạn chỉ cần một boolean. Nó cũng làm cho một sự khác biệt cho dù việc tránh nhiều cuộc gọi đến các chức năng này là quan trọng. Nó cũng có thể quan trọng nếu các hàm lấy bất kỳ hoặc các bộ tham số khác nhau. Nếu không có những giải thích rõ ràng này, tôi nghĩ rằng câu hỏi này rơi vào một trong những điều không rõ ràng, quá rộng hoặc dựa trên ý kiến.
jpmc26

7
@ jpmc26 OP nói rõ ràng về các giá trị trả về trung thực, và sau đó mã của anh ta trả về x(trái ngược với bool(x)) vì vậy tôi nghĩ an toàn khi cho rằng các chức năng của OP có thể trả về bất cứ điều gì và anh ta muốn bất cứ điều gì đầu tiên là sự thật.
gian 22/03/2016

Câu trả lời:


278

Bạn có thể sử dụng một vòng lặp:

conditions = (check_size, check_color, check_tone, check_flavor)
for condition in conditions:
    result = condition()
    if result:
        return result

Điều này có thêm lợi thế là bây giờ bạn có thể biến số điều kiện thành biến.

Bạn có thể sử dụng map()+ filter()(phiên bản Python 3, sử dụng các future_builtinsphiên bản trong Python 2) để nhận giá trị khớp đầu tiên như vậy:

try:
    # Python 2
    from future_builtins import map, filter
except ImportError:
    # Python 3
    pass

conditions = (check_size, check_color, check_tone, check_flavor)
return next(filter(None, map(lambda f: f(), conditions)), None)

nhưng nếu điều này dễ đọc hơn là tranh cãi.

Một tùy chọn khác là sử dụng biểu thức trình tạo:

conditions = (check_size, check_color, check_tone, check_flavor)
checks = (condition() for condition in conditions)
return next((check for check in checks if check), None)

27
nếu các điều kiện thực sự chỉ là điều kiện, tức là booleans thì trong đề xuất đầu tiên của bạn, bạn cũng có thể sử dụng nội dung anythay vì vòng lặp. return any(condition() for condition in conditions)

4
@Leonhard: anycó gần như cùng thực hiện bên trong. Nhưng có vẻ tốt hơn, xin vui lòng gửi nó như một câu trả lời)
Nick Volynkin

13
Khả năng đọc hơn hẳn các cân nhắc khác. Bạn nói, bản đồ / bộ lọc là 'gây tranh cãi', tôi đã bỏ phiếu cho sự xấu xí không thể chối cãi. Chắc chắn, cảm ơn, nhưng nếu bất cứ ai trong nhóm của tôi đưa bản đồ / bộ lọc vào mã này, tôi sẽ chuyển chúng cho một nhóm khác hoặc giao chúng cho nhiệm vụ bedpan.
Kevin J. Rice

15
Là khối mã không thể đọc được này thực sự "pythonia"? Và đặc biệt là ý tưởng nén conditionsarguments? Đây là IMHO tồi tệ hơn nhiều so với mã ban đầu, mất khoảng 10 giây để phân tích cú pháp bởi bộ não của tôi.
yo '

34
"Thích Python", họ nói. "Perl là không thể đọc được", họ nói. Và sau đó điều này đã xảy ra : return next((check for check in checks if check), None).
jja

393

Ngoài ra, với câu trả lời hay của Martijn, bạn có thể xâu chuỗi or. Điều này sẽ trả về giá trị trung thực đầu tiên hoặc Nonenếu không có giá trị trung thực:

def check_all_conditions():
    return check_size() or check_color() or check_tone() or check_flavor() or None

Bản giới thiệu:

>>> x = [] or 0 or {} or -1 or None
>>> x
-1
>>> x = [] or 0 or {} or '' or None
>>> x is None
True

9
Chắc chắn, nhưng điều này sẽ trở nên tẻ nhạt để đọc nhanh nếu có nhiều hơn một vài lựa chọn. Cộng với cách tiếp cận của tôi cho phép bạn sử dụng một số điều kiện khác nhau.
Martijn Pieters

14
@MartijnPieters bạn có thể sử dụng \để đặt mỗi kiểm tra trên dòng riêng của mình.
Caridorc 20/03/2016

12
@MartijnPieters Tôi chưa bao giờ ngụ ý câu trả lời của tôi tốt hơn câu trả lời của bạn, tôi cũng thích câu trả lời của bạn :)
vòng quay

38
@Caridorc: Tôi không thích sử dụng \để mở rộng dòng logic. Sử dụng dấu ngoặc đơn nếu có thể thay thế; vì vậy return (....)với các dòng mới được chèn khi cần thiết. Tuy nhiên, đó sẽ là một dòng logic dài.
Martijn Pieters

47
Tôi nghĩ rằng đây là giải pháp tốt hơn. Đối số "sẽ trở nên tẻ nhạt [..] nếu có nhiều hơn một vài tùy chọn" là tranh luận, bởi vì một chức năng duy nhất không nên tạo ra một số lượng kiểm tra cắt cổ. Nếu điều đó là bắt buộc, kiểm tra nên được chia thành nhiều chức năng.
BlueRaja - Daniel Pflughoeft

88

Đừng thay đổi nó

Có nhiều cách khác để làm điều này như các câu trả lời khác nhau cho thấy. Không có gì rõ ràng như mã ban đầu của bạn.


39
Tôi muốn tranh luận về điều đó, nhưng đề nghị của bạn là một ý kiến ​​hợp pháp để được lên tiếng. Cá nhân, tôi thấy mắt mình căng thẳng khi cố gắng đọc OP trong khi, ví dụ, giải pháp bấm giờ nhấp chuột ngay lập tức.
Reti43

3
Đó thực sự là một vấn đề quan điểm. Cá nhân tôi, tôi sẽ loại bỏ các dòng mới sau :, bởi vì tôi if x: return xcho là khá tốt, và nó làm cho chức năng trông gọn hơn. Nhưng đó có thể chỉ là tôi.
yo '

2
Đó không chỉ là bạn. Sử dụng ornhư thời gian đã làm là một thành ngữ thích hợp và được hiểu rõ. Nhiều ngôn ngữ có điều này; có lẽ khi nó được gọi orelsethì nó thậm chí còn rõ ràng hơn, nhưng thậm chí cũ đơn giản or(hoặc ||trong các ngôn ngữ khác) có nghĩa được hiểu là sự thay thế để thử nếu cái đầu tiên "không hoạt động."
Ray Toal

1
@RayToal: Nhập thành ngữ từ các ngôn ngữ khác là một cách tuyệt vời để làm xáo trộn mã.
Jack Aidley

1
Đôi khi có, chắc chắn! Ngoài ra, nó có thể là một cách để mở mang đầu óc và dẫn dắt người ta khám phá những mô hình và mô hình mới và tốt hơn mà trước đây không ai có thể thử. Phong cách phát triển bởi những người mượn và chia sẻ và thử những điều mới. Hoạt động cả hai cách. Dù sao, tôi chưa bao giờ nghe thấy việc sử dụng ornhãn hiệu không phải là Pythonic hoặc trong bất kỳ cách nào bị xáo trộn, nhưng dù sao đó cũng là một vấn đề quan điểm --- như nó phải vậy.
Ray Toal

83

Trong câu trả lời có hiệu quả tương tự như dòng thời gian, nhưng bạn có thể sử dụng dấu ngoặc đơn để định dạng đẹp hơn:

def check_all_the_things():
    return (
        one()
        or two()
        or five()
        or three()
        or None
    )

8
mọi người hãy giúp nâng câu trả lời này lên vị trí số 1 làm phần của bạn
Gyom

74

Theo luật của xoăn , bạn có thể làm cho mã này dễ đọc hơn bằng cách chia hai mối quan tâm:

  • Những gì tôi kiểm tra?
  • Có một điều trở lại đúng?

thành hai chức năng:

def all_conditions():
    yield check_size()
    yield check_color()
    yield check_tone()
    yield check_flavor()

def check_all_conditions():
    for condition in all_conditions():
        if condition:
            return condition
    return None

Điều này tránh:

  • cấu trúc logic phức tạp
  • những hàng thật dài
  • sự lặp lại

... Trong khi bảo tồn một luồng tuyến tính, dễ đọc.

Bạn có thể cũng có thể đưa ra các tên hàm thậm chí tốt hơn, theo hoàn cảnh cụ thể của bạn, làm cho nó thậm chí còn dễ đọc hơn.


Tôi thích cái này, mặc dù Đúng / Sai nên được thay đổi thành điều kiện / Không có gì phù hợp với câu hỏi.
Malcolm

2
Đó là sở thích của tôi! Nó đối phó với các kiểm tra và đối số khác nhau quá. Khá có thể được thiết kế quá mức cho ví dụ cụ thể này nhưng là một công cụ thực sự hữu ích cho các vấn đề trong tương lai!
rjh

4
Lưu ý rằng return Nonekhông cần thiết, bởi vì các hàm trả về Nonetheo mặc định. Tuy nhiên, không có gì sai khi trả lại Nonemột cách rõ ràng và tôi thích rằng bạn đã chọn làm như vậy.
hồ bấm giờ

1
Tôi nghĩ cách tiếp cận này sẽ được thực hiện tốt hơn với định nghĩa hàm cục bộ.
Jack Aidley

1
@timride "Rõ ràng là tốt hơn ngầm định", Zen của Python .
jpmc26

42

Đây là một biến thể của ví dụ đầu tiên của Martijns. Nó cũng sử dụng kiểu "bộ sưu tập các cuộc gọi" để cho phép đoản mạch.

Thay vì một vòng lặp, bạn có thể sử dụng nội dung any.

conditions = (check_size, check_color, check_tone, check_flavor)
return any(condition() for condition in conditions) 

Lưu ý rằng anytrả về boolean, vì vậy nếu bạn cần giá trị trả về chính xác của séc, giải pháp này sẽ không hoạt động. anysẽ không phân biệt được giữa 14, 'red', 'sharp', 'spicy'như các giá trị trở lại, tất cả họ sẽ được trả lại như True.


Bạn có thể làm next(itertools.ifilter(None, (c() for c in conditions)))để có được giá trị thực mà không cần đưa nó vào boolean.
kojiro

1
anythực sự ngắn mạch?
zwol 22/03/2016

1
@zwol Có thử nó với một số chức năng mẫu hoặc xem docs.python.org/3/library/functions.html

1
Điều này ít đọc hơn so với việc xâu chuỗi 4 hàm bằng 'hoặc' và chỉ trả hết nếu số lượng điều kiện lớn hoặc động.
rjh

1
@rjh Nó hoàn toàn có thể đọc được; nó chỉ là một danh sách theo nghĩa đen và một sự hiểu biết. Tôi thích nó hơn bởi vì đôi mắt của tôi sáng lên sau khoảng thứ bax = bar(); if x: return x;
Blacklight Shining

27

Bạn đã xem xét chỉ viết if x: return xtất cả trên một dòng?

def check_all_conditions():
    x = check_size()
    if x: return x

    x = check_color()
    if x: return x

    x = check_tone()
    if x: return x

    x = check_flavor()
    if x: return x

    return None

Đây không phải là ít lặp đi lặp lại so với những gì bạn đã có, nhưng IMNSHO nó đọc khá mượt mà.


24

Tôi khá ngạc nhiên khi không ai đề cập đến phần tích hợp anyđược tạo ra cho mục đích này:

def check_all_conditions():
    return any([
        check_size(),
        check_color(),
        check_tone(),
        check_flavor()
    ])

Lưu ý rằng mặc dù việc triển khai này có thể là rõ ràng nhất, nhưng nó đánh giá tất cả các kiểm tra ngay cả khi kiểm tra đầu tiên là True.


Nếu bạn thực sự cần dừng lại ở lần kiểm tra thất bại đầu tiên, hãy xem xét sử dụng reducedanh sách được thực hiện để chuyển đổi danh sách thành giá trị đơn giản:

def check_all_conditions():
    checks = [check_size, check_color, check_tone, check_flavor]
    return reduce(lambda a, f: a or f(), checks, False)

reduce(function, iterable[, initializer]): Áp dụng hàm của hai đối số tích lũy cho các mục của iterable, từ trái sang phải, để giảm số lần lặp xuống một giá trị. Đối số bên trái, x, là giá trị tích lũy và đối số bên phải, y, là giá trị cập nhật từ lần lặp. Nếu trình khởi tạo tùy chọn có mặt, nó được đặt trước các mục của phép lặp trong phép tính

Trong trường hợp của bạn:

  • lambda a, f: a or f()là chức năng kiểm tra xem bộ tích lũy ahoặc kiểm tra hiện tại f()True. Lưu ý rằng nếu aTrue, f()sẽ không được đánh giá.
  • checkschứa các chức năng kiểm tra ( fmục từ lambda)
  • False là giá trị ban đầu, nếu không sẽ không có kiểm tra nào xảy ra và kết quả sẽ luôn là True

anyreducelà các công cụ cơ bản để lập trình chức năng. Tôi đặc biệt khuyến khích bạn đào tạo những điều này cũng như mapđó là tuyệt vời quá!


9
anychỉ hoạt động nếu kiểm tra thực sự trả về giá trị boolean, theo nghĩa đen Truehoặc False, nhưng câu hỏi không chỉ định điều đó. Bạn cần sử dụng reduceđể trả về giá trị thực mà séc trả về. Ngoài ra, thật dễ dàng để tránh đánh giá tất cả các kiểm tra anybằng cách sử dụng một trình tạo, ví dụ any(c() for c in (check_size, check_color, check_tone, check_flavor)). Như trong câu trả lời của Leonhard
David Z

Tôi thích lời giải thích và cách sử dụng của bạn reduce. Giống như @DavidZ Tôi tin rằng giải pháp của bạn anynên sử dụng một trình tạo và nó cần được chỉ ra rằng nó bị giới hạn khi trả về Truehoặc False.
hồ bấm giờ

1
@DavidZ thực sự anyhoạt động với các giá trị trung thực: any([1, "abc", False]) == Trueany(["", 0]) == False
ngasull 22/03/2016

3
@blint xin lỗi, tôi đã không rõ ràng. Mục tiêu của câu hỏi là trả về kết quả của séc (và không chỉ đơn thuần là cho biết séc thành công hay thất bại). Tôi đã chỉ ra rằng anychỉ hoạt động cho mục đích đó nếu các giá trị boolean thực tế được trả về từ các hàm kiểm tra.
David Z

19

Nếu bạn muốn cấu trúc mã tương tự, bạn có thể sử dụng các câu lệnh ternary!

def check_all_conditions():
    x = check_size()
    x = x if x else check_color()
    x = x if x else check_tone()
    x = x if x else check_flavor()

    return x if x else None

Tôi nghĩ rằng điều này có vẻ tốt đẹp và rõ ràng nếu bạn nhìn vào nó.

Bản giới thiệu:

Ảnh chụp màn hình của nó đang chạy


7
Có chuyện gì với con cá ASCII nhỏ phía trên thiết bị đầu cuối của bạn?

36
@LegoStormtroopr Tôi sử dụng vỏ cá, vì vậy tôi trang trí nó bằng một bể cá ascii để làm tôi vui. :)
Phinet

3
Cảm ơn vì những con cá tốt (và màu sắc bằng cách này, trình soạn thảo đó là gì?)
toán học

4
Bạn có thể lấy cá tại fishshell.com và tệp cấu hình cho ascii tại đây pastebin.com/yYVYvVeK , cũng là trình soạn thảo là văn bản tuyệt vời.
Phinet

9
x if x else <something>chỉ có thể được giảm xuốngx or <something>

5

Đối với tôi, câu trả lời tốt nhất là từ @ phil-frost, tiếp theo là @ wayne-werner's.

Điều tôi cảm thấy thú vị là không ai nói gì về thực tế rằng một hàm sẽ trả về nhiều loại dữ liệu khác nhau, điều này sẽ khiến sau đó bắt buộc phải kiểm tra loại x để thực hiện bất kỳ công việc nào khác.

Vì vậy, tôi sẽ trộn phản hồi của @ PhilFrost với ý tưởng giữ một loại duy nhất:

def all_conditions(x):
    yield check_size(x)
    yield check_color(x)
    yield check_tone(x)
    yield check_flavor(x)

def assessed_x(x,func=all_conditions):
    for condition in func(x):
        if condition:
            return x
    return None

Lưu ý rằng xđược thông qua như là một đối số, nhưng cũng all_conditionsđược sử dụng như một trình tạo các hàm kiểm tra được thông qua trong đó tất cả chúng đều xđược kiểm tra và trả về Truehoặc False. Bằng cách sử dụng funcvới all_conditionsgiá trị mặc định, bạn có thể sử dụng assessed_x(x)hoặc bạn có thể chuyển một trình tạo được cá nhân hóa thêm thông qua func.

Bằng cách đó, bạn nhận được xngay khi một kiểm tra vượt qua, nhưng nó sẽ luôn là cùng một loại.


4

Lý tưởng nhất, tôi sẽ viết lại các check_ hàm để trả về Truehoặc Falsehơn là một giá trị. Séc của bạn sau đó trở thành

if check_size(x):
    return x
#etc

Giả sử bạn xkhông phải là bất biến, chức năng của bạn vẫn có thể sửa đổi nó (mặc dù họ không thể gán lại nó) - nhưng checkdù sao thì một chức năng được gọi là không nên sửa đổi nó.


3

Một biến thể nhỏ trên ví dụ đầu tiên của Martijns ở trên, sẽ tránh được nếu bên trong vòng lặp:

Status = None
for c in [check_size, check_color, check_tone, check_flavor]:
  Status = Status or c();
return Status

Phải không? Bạn vẫn làm một so sánh. Trong phiên bản của bạn, bạn cũng sẽ kiểm tra tất cả các điều kiện bất kể và không trả về trong trường hợp đầu tiên của một giá trị trung thực, Phụ thuộc vào mức độ tốn kém của các chức năng đó, điều đó có thể không được mong muốn.
Reti43

4
@ Reti43: Status or c()sẽ bỏ qua / short-Circui đánh giá các cuộc gọi đến c()nếu Statuslà sự thật, vì vậy mã trong câu trả lời này dường như không gọi bất kỳ chức năng nào nhiều hơn mã của OP. stackoverflow.com/questions/2580136/
Slater Neil

2
@NeilSlater Đúng. Nhược điểm duy nhất tôi thấy là trường hợp tốt nhất bây giờ là ở O (n) vì trình liệt kê phải mang lại n lần, khi đó là O (1) trước nếu hàm đầu tiên trả về giá trị trung thực trong O (1).
hồ bấm giờ

1
Có điểm tốt. Tôi chỉ cần hy vọng rằng c () mất nhiều thời gian hơn để đánh giá hơn là lặp một vòng lặp gần như trống rỗng. Kiểm tra hương vị có thể mất cả một buổi tối ít nhất nếu nó là một tốt.
toán học

3

Tôi thích @ tim xoay của. Trong khi đó, tôi muốn nói thêm rằng việc thể hiện Nonetrong returntuyên bố là không cần thiết vì bộ sưu tập các orcâu lệnh tách biệt được đánh giá và không có gì đầu tiên, không trống, không ai được trả về và nếu không có thì Nonesẽ được trả về có Nonehay không có!

Vì vậy, check_all_conditions()chức năng của tôi trông như thế này:

def check_all_conditions():
    return check_size() or check_color() or check_tone() or check_flavor()

Sử dụng timeitvới number=10**7tôi đã xem xét thời gian chạy của một số gợi ý. Để so sánh, tôi chỉ sử dụng random.random()hàm để trả về một chuỗi hoặc Nonedựa trên các số ngẫu nhiên. Đây là toàn bộ mã:

import random
import timeit

def check_size():
    if random.random() < 0.25: return "BIG"

def check_color():
    if random.random() < 0.25: return "RED"

def check_tone():
    if random.random() < 0.25: return "SOFT"

def check_flavor():
    if random.random() < 0.25: return "SWEET"

def check_all_conditions_Bernard():
    x = check_size()
    if x:
        return x

    x = check_color()
    if x:
        return x

    x = check_tone()
    if x:
        return x

    x = check_flavor()
    if x:
        return x
    return None

def check_all_Martijn_Pieters():
    conditions = (check_size, check_color, check_tone, check_flavor)
    for condition in conditions:
        result = condition()
        if result:
            return result

def check_all_conditions_timgeb():
    return check_size() or check_color() or check_tone() or check_flavor() or None

def check_all_conditions_Reza():
    return check_size() or check_color() or check_tone() or check_flavor()

def check_all_conditions_Phinet():
    x = check_size()
    x = x if x else check_color()
    x = x if x else check_tone()
    x = x if x else check_flavor()

    return x if x else None

def all_conditions():
    yield check_size()
    yield check_color()
    yield check_tone()
    yield check_flavor()

def check_all_conditions_Phil_Frost():
    for condition in all_conditions():
        if condition:
            return condition

def main():
    num = 10000000
    random.seed(20)
    print("Bernard:", timeit.timeit('check_all_conditions_Bernard()', 'from __main__ import check_all_conditions_Bernard', number=num))
    random.seed(20)
    print("Martijn Pieters:", timeit.timeit('check_all_Martijn_Pieters()', 'from __main__ import check_all_Martijn_Pieters', number=num))
    random.seed(20)
    print("timgeb:", timeit.timeit('check_all_conditions_timgeb()', 'from __main__ import check_all_conditions_timgeb', number=num))
    random.seed(20)
    print("Reza:", timeit.timeit('check_all_conditions_Reza()', 'from __main__ import check_all_conditions_Reza', number=num))
    random.seed(20)
    print("Phinet:", timeit.timeit('check_all_conditions_Phinet()', 'from __main__ import check_all_conditions_Phinet', number=num))
    random.seed(20)
    print("Phil Frost:", timeit.timeit('check_all_conditions_Phil_Frost()', 'from __main__ import check_all_conditions_Phil_Frost', number=num))

if __name__ == '__main__':
    main()

Và đây là kết quả:

Bernard: 7.398444877040768
Martijn Pieters: 8.506569201346597
timgeb: 7.244275416364456
Reza: 6.982133448743038
Phinet: 7.925932800076634
Phil Frost: 11.924794811353031

2

Cách này là một chút bên ngoài của hộp, nhưng tôi nghĩ rằng kết quả cuối cùng là đơn giản, dễ đọc và trông đẹp.

Ý tưởng cơ bản là raisemột ngoại lệ khi một trong các hàm đánh giá là trung thực và trả về kết quả. Đây là cách nó có thể trông:

def check_conditions():
    try:
        assertFalsey(
            check_size,
            check_color,
            check_tone,
            check_flavor)
    except TruthyException as e:
        return e.trigger
    else:
        return None

Bạn sẽ cần một assertFalseyhàm làm tăng ngoại lệ khi một trong các đối số hàm được gọi là đánh giá là trung thực:

def assertFalsey(*funcs):
    for f in funcs:
        o = f()
        if o:
            raise TruthyException(o)

Ở trên có thể được sửa đổi để cũng cung cấp các đối số cho các chức năng được đánh giá.

Và tất nhiên bạn sẽ cần TruthyExceptionchính nó. Ngoại lệ này cung cấp ngoại lệ objectkích hoạt ngoại lệ:

class TruthyException(Exception):
    def __init__(self, obj, *args):
        super().__init__(*args)
        self.trigger = obj

Tất nhiên, bạn có thể biến chức năng ban đầu thành một cái gì đó tổng quát hơn:

def get_truthy_condition(*conditions):
    try:
        assertFalsey(*conditions)
    except TruthyException as e:
        return e.trigger
    else:
        return None

result = get_truthy_condition(check_size, check_color, check_tone, check_flavor)

Điều này có thể chậm hơn một chút vì bạn đang sử dụng cả ifcâu lệnh và xử lý ngoại lệ. Tuy nhiên, ngoại lệ chỉ được xử lý tối đa một lần, do đó, lần truy cập hiệu suất chỉ là nhỏ trừ khi bạn muốn chạy kiểm tra và nhận được Truegiá trị nhiều nghìn lần.


// , Dễ thương! Có được coi là "Pythonic" để sử dụng xử lý ngoại lệ cho loại điều này không?
Nathan Basan

@NathanBasan Chắc chắn ngoại lệ được sử dụng cho luồng điều khiển mọi lúc. StopIterationlà một ví dụ khá hay: một ngoại lệ được đưa ra mỗi khi bạn sử dụng một lần lặp. Điều bạn muốn tránh là liên tục đưa ra các ngoại lệ lặp đi lặp lại, điều này sẽ trở nên đắt đỏ. Nhưng làm một lần thì không.
Rick ủng hộ Monica

//, Ah, tôi hiểu rằng bạn đang đề cập đến một cái gì đó như lập trình viên.stackexchange.com / questions / 12463 / . Tôi đã bỏ phiếu cho câu hỏi đó và cho câu trả lời này. Tài liệu Python 3 cho điều này có ở đây: docs.python.org/3/l Library / stdtypes.html#iterator-types , tôi nghĩ vậy.
Nathan Basan

1
Bạn muốn xác định một hàm mục đích chung và một ngoại lệ, chỉ cần thực hiện một vài kiểm tra trong một số chức năng khác ở đâu đó? Tôi nghĩ đó là một chút nhiều.
Blacklight Shining

@BacklightShining Tôi đồng ý. Tôi sẽ không bao giờ thực sự làm điều này bản thân mình. OP đã yêu cầu các cách để tránh mã lặp đi lặp lại, nhưng tôi nghĩ những gì anh ấy bắt đầu là hoàn toàn tốt.
Rick ủng hộ Monica

2

Cách pythonic là sử dụng less (như ai đó đã đề cập) hoặc itertools (như hiển thị bên dưới), nhưng đối với tôi, chỉ cần sử dụng ngắn mạch của ortoán tử sẽ tạo ra mã rõ ràng hơn

from itertools import imap, dropwhile

def check_all_conditions():
    conditions = (check_size,\
        check_color,\
        check_tone,\
        check_flavor)
    results_gen = dropwhile(lambda x:not x, imap(lambda check:check(), conditions))
    try:
        return results_gen.next()
    except StopIteration:
        return None

0

Tôi sẽ nhảy vào đây và chưa bao giờ viết một dòng Python nào, nhưng tôi cho if x = check_something(): return xlà hợp lệ?

nếu vậy:

def check_all_conditions():

    if (x := check_size()): return x
    if (x := check_color()): return x
    if (x := check_tone()): return x
    if (x := check_flavor()): return x

    return None

1
Nó không hợp lệ Python, không. Python không cho phép bạn sử dụng toán tử gán như thế. Tuy nhiên, một biểu thức gán đặc biệt mới đã được thêm vào gần đây, vì vậy bây giờ bạn có thể viết if ( x := check_size() ) :cho cùng một hiệu ứng.
Jack Aidley

0

Hoặc sử dụng max:

def check_all_conditions():
    return max(check_size(), check_color(), check_tone(), check_flavor()) or None

-2

Tôi đã thấy một số triển khai thú vị của các báo cáo chuyển đổi / trường hợp với các dấu hiệu trong quá khứ dẫn tôi đến câu trả lời này. Sử dụng ví dụ bạn đã cung cấp, bạn sẽ nhận được những điều sau đây. (Thật điên rồ using_complete_sentences_for_function_names, vì vậy check_all_conditionsđược đổi tên thành status. Xem (1))

def status(k = 'a', s = {'a':'b','b':'c','c':'d','d':None}) :
  select = lambda next, test : test if test else next
  d = {'a': lambda : select(s['a'], check_size()  ),
       'b': lambda : select(s['b'], check_color() ),
       'c': lambda : select(s['c'], check_tone()  ),
       'd': lambda : select(s['d'], check_flavor())}
  while k in d : k = d[k]()
  return k

Hàm select sẽ loại bỏ nhu cầu gọi check_FUNCTIONhai lần tức là bạn tránh check_FUNCTION() if check_FUNCTION() else nextbằng cách thêm một lớp chức năng khác. Điều này rất hữu ích cho các chức năng chạy dài. Các lambdas trong việc thực thi độ trễ dict của các giá trị của nó cho đến vòng lặp while.

Là một phần thưởng, bạn có thể sửa đổi thứ tự thực hiện và thậm chí bỏ qua một số thử nghiệm bằng cách thay đổi ksví dụ như k='c',s={'c':'b','b':None}giảm số lượng thử nghiệm và đảo ngược thứ tự xử lý ban đầu.

Các timeitnghiên cứu sinh có thể mặc cả về chi phí thêm một hoặc hai lớp bổ sung vào ngăn xếp và chi phí cho việc tìm kiếm chính tả nhưng bạn có vẻ quan tâm nhiều hơn đến tính hay của mã.

Ngoài ra, việc thực hiện đơn giản hơn có thể là như sau:

def status(k=check_size) :
  select = lambda next, test : test if test else next
  d = {check_size  : lambda : select(check_color,  check_size()  ),
       check_color : lambda : select(check_tone,   check_color() ),
       check_tone  : lambda : select(check_flavor, check_tone()  ),
       check_flavor: lambda : select(None,         check_flavor())}
  while k in d : k = d[k]()
  return k
  1. Ý tôi là điều này không phải ở khía cạnh pep8 mà là về cách sử dụng một từ mô tả ngắn gọn thay cho câu. Cấp cho OP có thể tuân theo một số quy ước mã hóa, làm việc một số cơ sở mã hiện có hoặc không quan tâm đến các điều khoản ngắn gọn trong cơ sở mã của họ.

1
Đôi khi mọi người thực sự phát điên với cách đặt tên của họ khi một từ sẽ làm. Sử dụng mã của OP làm ví dụ không chắc là anh ta sẽ có các hàm được gọi check_no/some/even/prime/every_third/fancy_conditionsnhưng chỉ có một hàm này, vậy tại sao không gọi nó statushoặc nếu một người khăng khăng check_status. Sử dụng _all_là không cần thiết, anh ta không đảm bảo tính toàn vẹn của vũ trụ. Đặt tên chắc chắn nên sử dụng một bộ từ khóa nhất quán tận dụng khoảng cách tên bất cứ khi nào có thể. Câu dài phục vụ tốt nhất như tài liệu. Một người hiếm khi cần nhiều hơn 8-10 ký tự để mô tả một cái gì đó súc tích.
Carel

1
Tôi là một fan hâm mộ của các tên hàm dài, vì tôi muốn các hàm cấp cao hơn sẽ tự ghi lại. Nhưng check_all_conditionslà một tên xấu, bởi vì nó không kiểm tra tất cả các điều kiện nếu một là đúng. Tôi sẽ sử dụng một cái gì đó như matches_any_condition.
John Hazen

Đó là một chiến thuật thú vị để thực hiện. Tôi cố gắng giảm thiểu số lượng chữ cái tôi sẽ viết sau này :) Có vẻ như tôi đã đưa ra một đống ý kiến ​​vào giải pháp của mình, khi tôi thực sự đang cố gắng đưa ra một gợi ý hữu ích. Điều này có nên được chỉnh sửa?
Carel

2
Điều này có vẻ quá hack, đặc biệt là xem xét các giải pháp khác cho câu hỏi này. Những gì OP đang cố gắng làm không hề phức tạp; giải pháp nên đủ đơn giản để hiểu được nửa ngủ. Và tôi không biết chuyện gì đang xảy ra ở đây.
Blacklight Shining

Tôi đã nhắm đến sự linh hoạt. Câu trả lời được sửa đổi để bao gồm một biến thể ít 'hacky' hơn
Carel
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.