Jak przerwać linię łańcuchowych metod w Pythonie?

138

Mam wiersz następującego kodu (nie obwiniaj konwencji nazewnictwa, nie są moje):

subkeyword = Session.query(
    Subkeyword.subkeyword_id, Subkeyword.subkeyword_word
).filter_by(
    subkeyword_company_id=self.e_company_id
).filter_by(
    subkeyword_word=subkeyword_word
).filter_by(
    subkeyword_active=True
).one()

Nie podoba mi się to, jak to wygląda (niezbyt czytelne), ale nie mam lepszego pomysłu na ograniczenie linii do 79 znaków w tej sytuacji. Czy jest lepszy sposób na jej złamanie (najlepiej bez ukośników)?

Juliusz Gonera
źródło

Odpowiedzi:

256

Możesz użyć dodatkowego nawiasu:

subkeyword = (
        Session.query(Subkeyword.subkeyword_id, Subkeyword.subkeyword_word)
        .filter_by(subkeyword_company_id=self.e_company_id)
        .filter_by(subkeyword_word=subkeyword_word)
        .filter_by(subkeyword_active=True)
        .one()
    )
sth
źródło
Ja też to lubię najbardziej. Nie dodaje więcej kodu i nie ma odwrotnego ukośnika.
Juliusz Gonera
22
Nie wiem, co uzasadnia dodatkowe wcięcie w tym miejscu; Myślę, że to rozwiązanie dobrze się czyta, gdy wiszące linie są wcięte tylko raz, a końcowy paren wcale nie jest.
Carl Meyer
4
Moim zdaniem podwójne wcięcie jest tutaj przydatne, ponieważ różni się wizualnie od zwykłego bloku z wcięciem. Gdy jest otoczony innym kodem, jest to bardziej oczywiste, że jest to opakowana pojedyncza linia.
sth
1
Najlepsza odpowiedź, jeśli chodzi o używanie parenów. Jak wspomniano w komentarzu Shanimala w innej odpowiedzi, użycie domniemanej kontynuacji linii w nawiasach jest w rzeczywistości preferowanym znakiem PEP 8 w porównaniu do znaku kontynuacji ''
kevlarr
Wolę ukośniki odwrotne. Nawias nie jest wskazówką na każdą sytuację. Na przykład nie działa z operatorem przypisania. Wyobraź sobie, że chcesz przełamać linie w tym łańcuchu:foo.set_default('bar', {}).set_default('spam', {}).set_default('eggs', {})['lol'] = 'yeah'
loutre
56

Jest to przypadek, w którym znak kontynuacji wiersza jest preferowany zamiast otwierania nawiasów. Potrzeba tego stylu staje się bardziej oczywista, gdy nazwy metod stają się dłuższe, a metody zaczynają przyjmować argumenty:

subkeyword = Session.query(Subkeyword.subkeyword_id, Subkeyword.subkeyword_word) \
                    .filter_by(subkeyword_company_id=self.e_company_id)          \
                    .filter_by(subkeyword_word=subkeyword_word)                  \
                    .filter_by(subkeyword_active=True)                           \
                    .one()

PEP 8 ma być interpretowany z rozsądkiem i dbałością o to, co praktyczne i piękne. Na szczęście naruszysz jakiekolwiek wytyczne PEP 8, które skutkują brzydkim lub trudnym do odczytania kodem.

Biorąc to pod uwagę, jeśli często znajdujesz się w sprzeczności z PEP 8, może to oznaczać, że istnieją problemy z czytelnością, które wykraczają poza twój wybór białych znaków :-)

Raymond Hettinger
źródło
2
+1 na odwrotnych ukośnikach i wyrównanie połączonych filtrów w tym konkretnym przypadku. Ta sytuacja pojawia się również w Django i jest najbardziej czytelna w ten sposób - ale w każdej innej sytuacji czuję, że frazy w nawiasach są lepsze (nie cierpię z powodu problemu „czy po odwrotnym ukośniku jest spacja?”). To powiedziawszy, umieszczenie frazy w nawiasach może posłużyć do osiągnięcia tego samego efektu - ale powoduje to przejście do trybu czytania Lispa w trakcie czytania Pythona, co mnie denerwuje.
zxq9
11
Nie widzę, w jaki sposób to rozwiązanie radzi sobie lepiej „gdy nazwy metod stają się dłuższe i metody zaczynają przyjmować argumenty” niż „zawijanie zewnętrznych nawiasów” lub „łamanie wiersza po każdym otwartym i przed każdym zamkniętym nawem” rozwiązania. W rzeczywistości gorzej radzi sobie z tym, ponieważ (przynajmniej jak pokazano tutaj) wymaga znacznie głębszego wcięcia dla każdej wiszącej linii.
Carl Meyer
1
Zbyt duże wcięcie dla wywołań filtrów. Wystarczyłaby jedna tabulacja lub 4 spacje. Również wyrównanie `` ... Ile sekund przytrzymałeś klawisz spacji? Generalnie sprzeciwiam się wszystkim sposobom, które wymagają wbijania tego klawisza spacji, jakby nie było jutra.
Zelphir Kaltstahl
2
fwiw, PEP8 brzmi: „Preferowanym sposobem zawijania długich wierszy jest użycie niejawnej kontynuacji wiersza w języku Python w nawiasach, nawiasach kwadratowych i nawiasach klamrowych. Długie wiersze można podzielić na wiele wierszy, zawijając wyrażenia w nawiasach. Należy ich używać zamiast używania odwrotnego ukośnika dla kontynuacji linii. " - Python.org Następnie omawiamy, kiedy odwrotne ukośniki mogą być odpowiednie
Shanimal,
Świetne odniesienie do PEP8! Irytującym problemem związanym z wyrównywaniem wszystkich .filterwywołań jest to, że jeśli zmienisz subkeywordna sub_keyword, musisz teraz naprawić wcięcie każdego wiersza tylko dlatego, że zmieniłeś nazwę zmiennej.
Niedobrze,
15

Mój osobisty wybór byłby następujący:

subkeyword = Session.query (
    Subkeyword.subkeyword_id,
    Subkeyword.subkeyword_word,
).Filtruj według(
    subkeyword_company_id = self.e_company_id,
    subkeyword_word = subkeyword_word,
    subkeyword_active = True,
).jeden()
pkoch
źródło
1
Zgadzam się, jeśli jest przekazywanych kilka parametrów, ale wygląda to brzydko, gdy parametry 0 lub 1 są wspólne. Na przykład: gist.github.com/andybak/b23b6ad9a68c7e1b794d
Andy Baker
1
Tak, ten styl ma zdegenerowane przypadki (jak każdy styl). Nie złamałbym wszystkich otwartych paren. Nic z tego mnie nie uszczęśliwia, ale oto kilka przypadków: gist.github.com/pkoch/8098c76614765750f769
pkoch
12

Wystarczy zapisać pośredni wynik / obiekt i wywołać na nim następną metodę, np

q = Session.query(Subkeyword.subkeyword_id, Subkeyword.subkeyword_word)
q = q.filter_by(subkeyword_company_id=self.e_company_id)
q = q.filter_by(subkeyword_word=subkeyword_word)
q = q.filter_by(subkeyword_active=True)
subkeyword = q.one()
Ivo van der Wijk
źródło
10
Działa to dobrze w przypadku zapytania, ale jako ogólny wzorzec, nie jestem tego taki pewien. Na przykład podczas łączenia w łańcuch w stylu Beautiful Soup team_members = soup.find(class_='section team').find_all('ul').find_all('li')zwracana wartość z każdego .find(...)wywołania nie ma team_membersjeszcze znaczenia .
Taylor Edmiston
1
@TaylorEdmiston Możesz mieć różne nazwy dla częściowych wyników kursu. Coś jak section = soup.find(class_='section team')i team_members = section.find_all('ul').find_all('li').
Jeyekomon,
4

Zgodnie z odniesieniem do języka Python
Możesz użyć odwrotnego ukośnika.
Lub po prostu go zniszcz. Jeśli nawias nie jest sparowany, Python nie potraktuje tego jako linii. W takich okolicznościach wcięcie kolejnych wierszy nie ma znaczenia.

Haozhun
źródło
4

To trochę inne rozwiązanie niż oferowane przez innych, ale moje ulubione, ponieważ czasami prowadzi do sprytnego metaprogramowania.

base = [Subkeyword.subkeyword_id, Subkeyword_word]
search = {
    'subkeyword_company_id':self.e_company_id,
    'subkeyword_word':subkeyword_word,
    'subkeyword_active':True,
    }
subkeyword = Session.query(*base).filter_by(**search).one()

To dobra technika tworzenia wyszukiwań. Przejrzyj listę warunków do wyszukania ze złożonego formularza zapytania (lub oparte na ciągach dedukcje dotyczące tego, czego szuka użytkownik), a następnie po prostu rozbij słownik do filtra.

Árni St. Sigurðsson
źródło
1

Wydaje się, że używasz SQLAlchemy, jeśli to prawda, sqlalchemy.orm.query.Query.filter_by()metoda przyjmuje wiele argumentów słów kluczowych, więc możesz napisać tak:

subkeyword = Session.query(Subkeyword.subkeyword_id,
                           Subkeyword.subkeyword_word) \
                    .filter_by(subkeyword_company_id=self.e_company_id,
                               subkeyword_word=subkeyword_word,
                               subkeyword_active=True) \
                    .one()

Ale byłoby lepiej:

subkeyword = Session.query(Subkeyword.subkeyword_id,
                           Subkeyword.subkeyword_word)
subkeyword = subkeyword.filter_by(subkeyword_company_id=self.e_company_id,
                                  subkeyword_word=subkeyword_word,
                                  subkeyword_active=True)
subkeuword = subkeyword.one()
minhee
źródło
+1 dla podpowiedzi SQLAlchemy filter_by (). To jest dobre dla tego przykładu, ale często używam zamiast tego filter (), który akceptuje tylko 1 warunek.
Juliusz Gonera
1

Lubię wciskać argumenty dwoma blokami, a oświadczenie jednym blokiem, na przykład:

for image_pathname in image_directory.iterdir():
    image = cv2.imread(str(image_pathname))
    input_image = np.resize(
            image, (height, width, 3)
        ).transpose((2,0,1)).reshape(1, 3, height, width)
    net.forward_all(data=input_image)
    segmentation_index = net.blobs[
            'argmax'
        ].data.squeeze().transpose(1,2,0).astype(np.uint8)
    segmentation = np.empty(segmentation_index.shape, dtype=np.uint8)
    cv2.LUT(segmentation_index, label_colours, segmentation)
    prediction_pathname = prediction_directory / image_pathname.name
    cv2.imwrite(str(prediction_pathname), segmentation)
akgtyrant
źródło