Pythoniczny sposób na uniknięcie instrukcji „if x: return x”


218

Mam metodę, która wywołuje 4 kolejne metody w celu sprawdzenia określonych warunków i zwraca natychmiast (nie sprawdzając kolejnych), ilekroć ktoś zwróci coś Prawdy.

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

To wygląda na dużo kodu bagażu. Zamiast każdej 2-liniowej instrukcji if wolę zrobić coś takiego:

x and return x

Ale to jest nieprawidłowy Python. Czy brakuje mi tutaj prostego, eleganckiego rozwiązania? Nawiasem mówiąc, w tej sytuacji te cztery metody sprawdzania mogą być drogie, więc nie chcę dzwonić do nich wiele razy.


7
Co to są te x? Czy są po prostu Prawdą / Fałszem, czy też są strukturami danych zawierającymi pewne informacje, przy czym Żaden lub podobny nie jest używany jako szczególny przypadek wskazujący na brak jakichkolwiek danych? Jeśli to drugie, prawie na pewno powinieneś używać wyjątków.
Nathaniel

13
@gerrit Przedstawiony powyżej kod jest hipotetyczny / pseudo-kod, który nie jest tematem w Code Review. Jeśli autor postu chce zweryfikować swój rzeczywisty, działający kod , to tak, może opublikować post w recenzji kodu.
Phrancis

4
Jak myślisz, dlaczego x and return xjest lepszy niż if x: return x? Ten ostatni jest znacznie bardziej czytelny, a tym samym łatwy do utrzymania. Nie powinieneś zbytnio przejmować się liczbą znaków lub linii; liczy się czytelność. W każdym razie są to dokładnie te same znaki, które nie są spacjami, i jeśli naprawdę musisz, if x: return xbędą działały dobrze tylko w jednym wierszu.
marcelm

3
Wyjaśnij, czy zależy Ci na rzeczywistych wartościach, czy naprawdę musisz po prostu zwrócić wartość logiczną. To robi różnicę, jakie opcje są dostępne, a które z nich wyraźniej przekazują zamiar. Nazewnictwo sugeruje, że potrzebujesz tylko wartości logicznej. Ma również znaczenie, czy ważne jest unikanie wielu wywołań tych funkcji. Może również mieć znaczenie, czy funkcje przyjmują dowolny lub inny zestaw parametrów. Bez tych wyjaśnień myślę, że pytanie to należy do niejasnego, zbyt szerokiego lub opartego na opiniach.
jpmc26

7
@ jpmc26 OP wyraźnie mówi o prawdziwych wartościach zwracanych, a następnie jego kod zwraca x(w przeciwieństwie do bool(x)), więc w obecnej postaci uważam, że można bezpiecznie założyć, że funkcje OP mogą zwrócić wszystko, a on chce najpierw wszystkiego, co jest zgodne z prawdą.
timgeb

Odpowiedzi:


278

Możesz użyć pętli:

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

Ma to tę dodatkową zaletę, że można teraz zmienić liczbę warunków.

Możesz użyć map()+ filter()(wersje Python 3, użyj future_builtinswersji Python 2), aby uzyskać pierwszą taką pasującą wartość:

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)

ale jeśli jest to bardziej czytelne, jest dyskusyjne.

Inną opcją jest użycie wyrażenia generatora:

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
jeśli warunki są tak naprawdę tylko warunkami, tj. booleanami, to w pierwszej propozycji można również użyć wbudowanego anyzamiast pętli. return any(condition() for condition in conditions)

4
@Leonhard: anyma prawie taką samą implementację w środku. Ale wygląda to znacznie lepiej, proszę opublikować jako odpowiedź)
Nick Volynkin

13
Czytelność przebija prawie wszystkie inne względy. Mówisz, że mapa / filtr jest „dyskusyjny”, głosuję za niewątpliwie brzydką. Dzięki, oczywiście, ale jeśli ktokolwiek z mojego zespołu wstawi mapę / filtr do tego kodu, przeniosę go do innego zespołu lub przydzielę do pracy w toalecie.
Kevin J. Rice

15
Czy ten nieczytelny blok kodu jest naprawdę „pythonian”? A szczególnie pomysł na zip conditionsi arguments? Jest to IMHO znacznie gorsze niż oryginalny kod, którego analiza zajmuje około 10 sekund.
yo

34
„Wolą Python”, powiedzieli. „Perl jest nieczytelny”, powiedzieli. I wtedy to się stało: return next((check for check in checks if check), None).
jja

393

Alternatywnie do dobrej odpowiedzi Martijna, możesz połączyć or. Zwróci pierwszą prawdziwą wartość lub Nonejeśli nie ma prawdziwej wartości:

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

Próbny:

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

9
Jasne, ale będzie to nużące, aby szybko czytać, jeśli istnieje więcej niż kilka opcji. Ponadto moje podejście pozwala na stosowanie zmiennej liczby warunków.
Martijn Pieters

14
@MartijnPieters możesz użyć, \aby umieścić każdy czek na osobnej linii.
Caridorc,

12
@MartijnPieters Nigdy nie sugerowałem, że moja odpowiedź jest lepsza niż twoja, ja też lubię twoją odpowiedź :)
timgeb

38
@Caridorc: Bardzo nie lubię używać \do przedłużania linii logicznej. Zamiast tego używaj nawiasów; więc return (....)z nowymi liniami wstawionymi w razie potrzeby. Będzie to jednak jedna długa logiczna linia.
Martijn Pieters

47
Myślę, że to lepsze rozwiązanie. Argument „stanie się nudny [..], jeśli jest więcej niż kilka opcji” jest dyskusyjny, ponieważ i tak żadna funkcja nie powinna wykonywać nadmiernej liczby kontroli. Jeśli jest to wymagane, kontrole należy podzielić na wiele funkcji.
BlueRaja - Danny Pflughoeft

88

Nie zmieniaj tego

Istnieją inne sposoby osiągnięcia tego, jak pokazują różne inne odpowiedzi. Żadne nie są tak jasne, jak oryginalny kod.


39
Byłbym przeciw temu, ale twoja sugestia jest uzasadniona. Osobiście uważam, że moje oczy są zmęczone, próbując odczytać OP, podczas gdy na przykład rozwiązanie timgeb klika natychmiast.
Reti43

3
To naprawdę kwestia opinii. Ja osobiście usunęłbym później nowe wiersze :, ponieważ uważam, że if x: return xjest całkiem w porządku, a to sprawia, że ​​funkcja wygląda na bardziej kompaktową. Ale to może być tylko ja.
yo

2
To nie tylko ty. Używanie ortak jak timgeb jest właściwym i dobrze rozumianym idiomem. Wiele języków ma to; być może kiedy jest nazywany orelse, jest jeszcze bardziej wyraźny, ale nawet zwykły stary or(lub ||w innych językach) ma być rozumiany jako alternatywa do wypróbowania, jeśli pierwszy „nie działa”.
Ray Toal

1
@RayToal: Importowanie idiomów z innych języków to świetny sposób na zaciemnienie kodu.
Jack Aidley

1
Czasami tak, na pewno! Może to być również sposób na otwarcie umysłu i doprowadzenie do odkrycia nowych, lepszych wzorców i paradygmatów, których nikt wcześniej nie próbował. Styl ewoluuje od ludzi pożyczających, dzielących się i próbujących nowych rzeczy. Działa w obie strony. W każdym razie nigdy nie słyszałem o używaniu oretykiet „nie Pythonic” ani w żaden sposób zaciemniających, ale i tak jest to kwestia opinii --- tak jak powinno być.
Ray Toal

83

W rzeczywistości taka sama odpowiedź jak timgeb, ale można użyć nawiasu do lepszego formatowania:

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

8
wszyscy proszę pomóżcie w podniesieniu tej odpowiedzi do 1. miejsca. Zrobić swoją rolę !
Gyom

74

Zgodnie z prawem Curly'ego możesz zwiększyć czytelność tego kodu, dzieląc dwie kwestie:

  • Jakie rzeczy sprawdzam?
  • Czy jedna rzecz zwróciła się prawda?

na dwie funkcje:

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

Pozwala to uniknąć:

  • skomplikowane struktury logiczne
  • naprawdę długie linie
  • powtórzenie

... zachowując liniowy, łatwy do odczytania przepływ.

Prawdopodobnie możesz również wymyślić jeszcze lepsze nazwy funkcji, w zależności od konkretnej sytuacji, co czyni ją jeszcze bardziej czytelną.


Podoba mi się ten, chociaż Prawda / Fałsz należy zmienić na warunek / Brak, aby dopasować do pytania.
Malcolm

2
To jest moje ulubione! Radzi sobie również z różnymi kontrolami i argumentami. Całkiem możliwe, że został przeprojektowany w tym konkretnym przykładzie, ale naprawdę przydatne narzędzie na przyszłe problemy!
rjh

4
Pamiętaj, że return Nonenie jest to konieczne, ponieważ funkcje zwracają Nonedomyślnie. Jednak nie ma nic złego w powrocie Nonejawnym i podoba mi się, że zdecydowałeś się to zrobić.
timgeb

1
Myślę, że to podejście lepiej byłoby wdrożyć dzięki definicji funkcji lokalnej.
Jack Aidley

1
@timgeb „Jawne jest lepsze niż niejawne”, Zen of Python .
jpmc26

42

Jest to wariant pierwszego przykładu Martijns. Wykorzystuje również styl „zbieranie kalabrów”, aby umożliwić zwarcie.

Zamiast pętli możesz użyć wbudowanego any.

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

Zwróć uwagę, że anyzwraca wartość logiczną, więc jeśli potrzebujesz dokładnej wartości zwrotu czeku, to rozwiązanie nie będzie działać. anynie rozróżnia 14, 'red', 'sharp', 'spicy'jako wartości zwracane, wszystkie będą zwracane jako True.


Możesz zrobić, next(itertools.ifilter(None, (c() for c in conditions)))aby uzyskać rzeczywistą wartość bez rzutowania jej na wartość logiczną.
kojiro

1
Czy anyfaktycznie powoduje zwarcie?
zwolnienie

1
@zwol Tak, spróbuj z kilkoma przykładowymi funkcjami lub zobacz docs.python.org/3/library/functions.html

1
Jest to mniej czytelne niż łączenie 4 funkcji za pomocą „lub” i opłaca się tylko wtedy, gdy liczba warunków jest duża lub dynamiczna.
rjh

1
@rjh Jest doskonale czytelny; to po prostu dosłowna lista i zrozumienie. Wolałbym, bo moje oczy błyszczą po około trzeciejx = bar(); if x: return x;
Blacklight Shining

27

Czy zastanawiałeś się nad pisaniem if x: return xwszystkiego w jednym wierszu?

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

To nie jest mniej powtarzalne niż to, co miałeś, ale IMNSHO czyta dość płynniej.


24

Jestem dość zaskoczony, że nikt nie wspomniał o wbudowanym, anyktóry został stworzony w tym celu:

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

Zauważ, że chociaż ta implementacja jest prawdopodobnie najczystsza, ocenia wszystkie kontrole, nawet jeśli pierwsza jest True.


Jeśli naprawdę musisz zatrzymać się przy pierwszym nieudanym sprawdzeniu, zastanów się nad użyciem tego, reduceco jest zrobione, aby przekonwertować listę do prostej wartości:

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]): Zastosuj funkcję dwóch argumentów łącznie do elementów iterowalnych, od lewej do prawej, aby zmniejszyć iterowalną wartość do pojedynczej wartości. Lewy argument x to skumulowana wartość, a prawy argument y to wartość aktualizacji z iterowalnego. Jeśli obecny jest opcjonalny inicjator, jest on umieszczany przed elementami iterowalnymi w obliczeniach

W Twoim przypadku:

  • lambda a, f: a or f()Jest to funkcja, która sprawdza, czy akumulator alub sprawdzić aktualny f()jest True. Pamiętaj, że jeśli atak True, f()to nie będzie oceniane.
  • checkszawiera funkcje sprawdzania ( fpozycja od lambda)
  • False jest wartością początkową, w przeciwnym razie nie nastąpiłoby sprawdzenie i wynik byłby zawsze True

anyi reducesą podstawowymi narzędziami do programowania funkcjonalnego. Gorąco zachęcam do ich trenowania, co również mapjest niesamowite!


9
anydziała tylko wtedy, gdy kontrole faktycznie zwracają wartość logiczną, dosłownie Truelub False, ale pytanie tego nie określa. Musisz użyć, reduceaby zwrócić rzeczywistą wartość zwróconą przez czek. Ponadto łatwo jest uniknąć oceny wszystkich kontroli anyza pomocą generatora, np any(c() for c in (check_size, check_color, check_tone, check_flavor)). Jak w odpowiedzi Leonharda
David Z

Podoba mi się twoje wyjaśnienie i sposób użycia reduce. Podobnie jak @DavidZ uważam, że twoje rozwiązanie anypowinno korzystać z generatora i należy zauważyć, że ogranicza się do zwrotu Truelub False.
timgeb

1
@DavidZ faktycznie anydziała z prawdziwymi wartościami: any([1, "abc", False]) == Trueiany(["", 0]) == False
ngasull

3
@blint przepraszam, nie było jasne. Celem pytania jest zwrócenie wyniku kontroli (a nie tylko wskazanie, czy kontrola się powiodła, czy nie). Wskazałem, że anydziała w tym celu tylko wtedy, gdy rzeczywiste wartości boolowskie są zwracane z funkcji kontrolnych.
David Z

19

Jeśli chcesz mieć taką samą strukturę kodu, możesz użyć instrukcji potrójnych!

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

Myślę, że wygląda to ładnie i wyraźnie, jeśli na to spojrzysz.

Próbny:

Zrzut ekranu z uruchomionym


7
Co z małą rybką ASCII nad terminalem?

36
@ LegoStormtroopr Używam skorupy ryb, więc dekoruję ją akwarium ascii, aby mnie uszczęśliwić. :)
Phinet

3
Dzięki za piękną rybę (i przy okazji kolory, jaki to edytor?)
Mathreadler

4
Możesz dostać ryby na fishshell.com , a plik konfiguracyjny dla ascii tutaj pastebin.com/yYVYvVeK , również edytor jest wysublimowanym tekstem.
Phinet

9
x if x else <something>można po prostu zmniejszyć dox or <something>

5

Dla mnie najlepsza odpowiedź to: @ phil-frost, a następnie @ wayne-werner.

Interesujące jest dla mnie to, że nikt nie powiedział nic o tym, że funkcja zwróci wiele różnych typów danych, co spowoduje, że wówczas będzie obowiązkowe sprawdzanie samego typu x, aby wykonać jakąkolwiek dalszą pracę.

Chciałbym więc połączyć odpowiedź @ PhilFrost z pomysłem zachowania jednego typu:

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

Zauważ, że xjest przekazywany jako argument, ale all_conditionsjest także używany jako przekazywany generator funkcji sprawdzających, w którym wszystkie z nich xsą sprawdzane i zwracają Truelub False. Używając funcz all_conditionsjako wartością domyślną, możesz użyć assessed_x(x)lub przekazać kolejny spersonalizowany generator za pośrednictwem func.

W ten sposób otrzymasz, xjak tylko jeden czek przejdzie, ale zawsze będzie tego samego typu.


4

Idealnie, chciałbym ponownie napisać check_ funkcje, aby powrócić Truelub Falsezamiast wartości. Twoje czeki stają się

if check_size(x):
    return x
#etc

Zakładając, że twoja xnie jest niezmienna, twoja funkcja może nadal ją modyfikować (chociaż nie mogą jej ponownie przypisać) - ale funkcja wywoływana i checktak nie powinna tak naprawdę modyfikować.


3

Niewielka odmiana pierwszego przykładu Martijns powyżej, która pozwala uniknąć wewnątrz pętli:

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

Czy to? Nadal robisz porównanie. W swojej wersji sprawdzisz także wszystkie warunki niezależnie i nie zwrócisz za pierwszym razem prawdziwej wartości, w zależności od tego, jak kosztowne są te funkcje, co może nie być pożądane.
Reti43

4
@ Reti43: Status or c()pominie / zwróci krótkie oceny połączeń, c()jeśli Statusjest prawdą, więc kod w tej odpowiedzi nie wydaje się wywoływać więcej funkcji niż kod PO. stackoverflow.com/questions/2580136/…
Neil Slater

2
@NeilSlater True. Jedynym minusem, jaki widzę, jest to, że najlepszym przypadkiem jest teraz O (n), ponieważ listiterator musi dawać n razy, gdy wcześniej było O (1), jeśli pierwsza funkcja zwraca coś prawdziwego w O (1).
timgeb

1
Tak, dobre punkty. Mam tylko nadzieję, że ocena c () zajmuje nieco więcej czasu niż zapętlenie prawie pustej pętli. Sprawdzanie smaku może zająć cały wieczór, przynajmniej jeśli jest dobry.
matreadler

3

Lubię @ timgeb's. W międzyczasie chciałbym dodać, że wyrażanie Nonew returninstrukcji nie jest potrzebne, ponieważ zbiór oroddzielnych instrukcji jest oceniany, a pierwsze zero-zero, nic-puste, żadne-Brak jest zwracane, a jeśli nie, to Nonejest zwracane czy istnieje Noneczy nie!

Więc moja check_all_conditions()funkcja wygląda następująco:

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

Używając timeitz number=10**7, spojrzałem na czas działania wielu sugestii. Dla porównania użyłem tej random.random()funkcji do zwrócenia ciągu znaków lub Nonena podstawie liczb losowych. Oto cały kod:

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()

A oto wyniki:

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

2

Ten sposób jest nieco poza pudełkiem, ale myślę, że wynik końcowy jest prosty, czytelny i ładnie wygląda.

Podstawową ideą jest raisewyjątek, gdy jedna z funkcji jest oceniana jako prawdziwa i zwraca wynik. Oto jak może to wyglądać:

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

Potrzebujesz assertFalseyfunkcji, która podnosi wyjątek, gdy jeden z wywoływanych argumentów funkcji jest oceniany jako prawdziwy:

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

Powyższe można zmodyfikować, aby dostarczyć argumentów do oceny funkcji.

I oczywiście będziesz potrzebować TruthyExceptionsamego siebie. Ten wyjątek zapewnia wyjątek, objectktóry wywołał wyjątek:

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

Oczywiście możesz zmienić oryginalną funkcję w coś bardziej ogólnego:

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)

Może to być nieco wolniejsze, ponieważ używasz zarówno ifinstrukcji, jak i obsługi wyjątku. Jednak wyjątek jest obsługiwany maksymalnie jeden raz, więc wpływ na wydajność powinien być niewielki, chyba że spodziewasz się uruchomienia testu i uzyskania Truewartości wiele tysięcy razy.


// , Uroczy! Czy uważa się za „Pythonic” korzystanie z obsługi wyjątków dla tego rodzaju rzeczy?
Nathan Basanese

@NathanBasanese Pewnie - wyjątki są używane do kontroli przepływu przez cały czas. StopIterationjest całkiem dobrym przykładem: wyjątek jest zgłaszany za każdym razem, gdy wyczerpujesz iterowalny. Rzeczą, której chcesz unikać, jest sukcesywne zgłaszanie wyjątków, które byłyby drogie. Ale zrobienie tego za jednym razem nie jest.
Rick wspiera Monikę

// Ach, biorę go masz na myśli coś podobnego programmers.stackexchange.com/questions/112463/... . Głosowałem za tym pytaniem i na tę odpowiedź. Dokumenty Pythona 3 do tego są tutaj: docs.python.org/3/library/stdtypes.html#iterator-types , tak myślę.
Nathan Basanese

1
Chcesz zdefiniować funkcję ogólnego przeznaczenia i wyjątek, po prostu zrobić kilka kontroli gdzieś w innej funkcji? Myślę, że to trochę za dużo.
Blacklight Shining

@ BacklightShining Zgadzam się. Nigdy sam bym tego nie zrobił. OP poprosił o sposoby uniknięcia powtarzającego się kodu, ale myślę, że to, od czego zaczął, jest w porządku.
Rick wspiera Monikę

2

Pythoniczny sposób albo wykorzystuje redukcję (jak ktoś już wspomniał) lub itertools (jak pokazano poniżej), ale wydaje mi się, że po prostu użycie zwarcia oroperatora daje wyraźniejszy kod

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

Wskoczę tutaj i nigdy nie napisałem ani jednej linii Pythona, ale zakładam, że if x = check_something(): return xjest poprawna?

w takim razie:

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
To nie jest poprawny Python, nie. Python nie pozwala ci używać takiego operatora przypisania. Jednak nowe specjalne wyrażenie przypisania zostało dodane niedawno, więc możesz teraz pisać if ( x := check_size() ) :dla tego samego efektu.
Jack Aidley

0

Lub użyj max:

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

-2

W przeszłości widziałem ciekawe implementacje instrukcji switch / case z dyktami, które doprowadziły mnie do tej odpowiedzi. Korzystając z podanego przez Ciebie przykładu, uzyskasz następujące informacje. (To szaleństwo using_complete_sentences_for_function_names, więc check_all_conditionsprzemianowano je na status. Zobacz (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

Funkcja wyboru eliminuje potrzebę check_FUNCTIONdwukrotnego wywoływania, tzn. Unikasz check_FUNCTION() if check_FUNCTION() else next, dodając kolejną warstwę funkcji. Jest to przydatne w przypadku długo działających funkcji. Lambdas w nagraniu opóźnia wykonanie jego wartości aż do pętli while.

Jako bonus można modyfikować kolejność wykonywania, a nawet pominąć niektóre badania przez przerabiania ki snpk='c',s={'c':'b','b':None} zmniejsza liczbę testów i odwraca pierwotną kolejność przetwarzania.

Ludzie timeitmogą się targować o koszt dodania dodatkowej warstwy lub dwóch do stosu i koszt dyktowania, ale wydajesz się bardziej zaniepokojony ładnością kodu.

Alternatywnie prostsza implementacja może wyglądać następująco:

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. Mam na myśli to nie w kategoriach pep8, ale w kategoriach użycia jednego zwięzłego słowa opisowego zamiast zdania. To prawda, że ​​OP może postępować zgodnie z jakąś konwencją kodowania, działając w oparciu o istniejącą bazę kodu lub nie dbając o krótkie terminy w swojej bazie kodu.

1
Czasami ludzie naprawdę oszaleli na punkcie swojego imienia, kiedy wystarczy jedno słowo. Używając kodu OP jako przykładu, jest mało prawdopodobne, że miałby wywoływane funkcje, check_no/some/even/prime/every_third/fancy_conditionsale tylko ta jedna funkcja, więc dlaczego nie zadzwonić statuslub jeśli ktoś nalega check_status. Używanie _all_jest zbyteczne, nie zapewnia integralności wszechświatów. Nazewnictwo powinno z pewnością używać spójnego zestawu słów kluczowych, wykorzystując odstępy między nazwami, jeśli to możliwe. Długie zdania najlepiej służą jako dokumenty. Rzadko potrzeba więcej niż 8-10 znaków, aby opisać coś zwięźle.
Carel

1
Jestem fanem długich nazw funkcji, ponieważ chcę, aby funkcje wyższego poziomu były samodokumentujące. Ale check_all_conditionsto zła nazwa, ponieważ nie sprawdza wszystkich warunków, jeśli jest to prawda. Użyłbym czegoś takiego matches_any_condition.
John Hazen

To interesujący takt. Staram się zminimalizować liczbę liter, na których będę później pisać literówki :) Wygląda na to, że włożyłem mnóstwo opinii do mojego rozwiązania, kiedy naprawdę starałem się podać pomocną wskazówkę. Czy należy to edytować?
Carel

2
Wydaje się to zbyt hackerskie, szczególnie biorąc pod uwagę inne rozwiązania tego pytania. To, co próbuje OP, wcale nie jest skomplikowane; rozwiązanie powinno być na tyle proste, aby zrozumieć, jak można zasnąć. I nie mam pojęcia, co się tutaj dzieje.
Blacklight Shining

Dążyłem do elastyczności. Zmodyfikowana odpowiedź, aby uwzględnić mniej „hacky” wariant
Carel
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.