Comment casser une ligne de méthodes chaînées en Python?

139

J'ai une ligne du code suivant (ne blâmez pas les conventions de nommage, elles ne sont pas les miennes):

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

Je n'aime pas à quoi ça ressemble (pas trop lisible) mais je n'ai pas de meilleure idée de limiter les lignes à 79 caractères dans cette situation. Existe-t-il un meilleur moyen de le casser (de préférence sans contre-obliques)?

Juliusz Gonera
la source

Réponses:

257

Vous pouvez utiliser des parenthèses supplémentaires:

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()
    )
qc
la source
Je l'aime aussi le plus. N'ajoute pas plus de code et c'est sans backslashes.
Juliusz Gonera
22
Je ne sais pas ce qui justifie l'indentation supplémentaire ici; Je pense que cette solution se lit aussi bien avec les lignes de suspension en retrait une seule fois et le paren de fin pas du tout.
Carl Meyer
4
À mon avis, la double indentation est utile ici car elle est visuellement distincte d'un bloc indenté normal. Lorsqu'il est entouré par un autre code, il est plus évident qu'il s'agit d'une seule ligne encapsulée.
STH
1
Meilleure réponse, en termes d'utilisation de parens. Comme mentionné dans un commentaire de Shanimal dans une autre réponse, l'utilisation de la continuation de ligne implicite via des parenthèses est en fait préférée à PEP 8 par rapport au caractère de continuation ``
kevlarr
Je préfère les contre-obliques. La parenthèse n'est pas un indice pour toutes les situations. Par exemple, cela ne fonctionne pas avec l'opérateur d'affectation. Imaginez que vous vouliez casser des lignes dans cette chaîne:foo.set_default('bar', {}).set_default('spam', {}).set_default('eggs', {})['lol'] = 'yeah'
loutre
56

C'est un cas où un caractère de continuation de ligne est préférable aux parenthèses ouvertes. La nécessité de ce style devient plus évidente à mesure que les noms de méthodes s'allongent et que les méthodes commencent à prendre des arguments:

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

La PEP 8 est destinée à être interprétée avec une mesure de bon sens et un œil à la fois pratique et beau. Violez heureusement toute directive PEP 8 qui entraîne un code laid ou difficile à lire.

Cela étant dit, si vous vous trouvez fréquemment en désaccord avec PEP 8, cela peut être un signe qu'il y a des problèmes de lisibilité qui transcendent votre choix d'espaces :-)

Raymond Hettinger
la source
2
+1 sur les contre-obliques et aligner les filtres enchaînés dans ce cas particulier. Cette situation se produit également dans Django et est la plus lisible de cette façon - mais dans toute autre situation, j'ai l'impression que les phrases entre parenthèses sont supérieures (ne souffrez pas du problème "y a-t-il des espaces après ma barre oblique inverse?"). Cela dit, mettre la phrase entre parenthèses peut être utilisée pour obtenir le même effet - mais cela vous met en mode de lecture Lisp au milieu de la lecture de Python, ce que je trouve discordant.
zxq9
11
Je ne vois pas comment cette solution est mieux à même de faire face "à mesure que les noms de méthode s'allongent et que les méthodes commencent à prendre des arguments" que le "wrap in external parens" ou "line-break after each open paren and before each close paren" solutions. En fait, c'est pire pour gérer cela, car (du moins comme indiqué ici), cela nécessite un retrait beaucoup plus profond pour chaque ligne suspendue.
Carl Meyer
1
Beaucoup trop de retrait pour les appels de filtre. Un onglet ou 4 espaces auraient suffi ici. Aussi alignement du `` ... Combien de secondes avez-vous maintenu cette touche d'espace? En général, je suis contre tous les moyens, qui vous obligent à marteler cette clé d'espace comme s'il n'y avait pas de lendemain.
Zelphir Kaltstahl
2
fwiw, PEP8 lit "La meilleure façon d'encapsuler les longues lignes est d'utiliser la continuation de ligne implicite de Python entre parenthèses, crochets et accolades. Les longues lignes peuvent être coupées sur plusieurs lignes en enveloppant les expressions entre parenthèses. Celles-ci doivent être utilisées de préférence à l'utilisation d'une barre oblique inverse pour la suite de la ligne. " - Python.org Il poursuit en discutant du moment où les barres obliques inverses peuvent être appropriées
Shanimal
Excellente référence à PEP8! Un problème ennuyeux ici avec l'alignement de tous les.filter appels est que si vous changez subkeyworden sub_keyword, vous devez maintenant corriger l'indentation de chaque ligne simplement parce que vous avez changé le nom de la variable. Pas bon quand le style entrave la maintenabilité ...
kevlarr
15

Mon choix personnel serait:

subkeyword = Session.query (
    Subkeyword.subkeyword_id,
    Subkeyword.subkeyword_word,
).filtrer par(
    subkeyword_company_id = self.e_company_id,
    subkeyword_word = sous-mot_clé,
    subkeyword_active = Vrai,
).une()
pkoch
la source
1
Je suis d'accord s'il y a plusieurs paramètres passés mais cela a l'air moche quand 0 ou 1 paramètres sont communs. Par exemple: gist.github.com/andybak/b23b6ad9a68c7e1b794d
Andy Baker
1
Ouais, ce style a des cas dégénérés (comme n'importe quel style). Je ne casserais pas sur toutes les parens ouvertes. Rien de tout cela ne me rend
pkoch
12

Stockez simplement le résultat / objet intermédiaire et invoquez la méthode suivante dessus, par exemple

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
la source
10
Cela fonctionne bien pour quelque chose comme une requête, mais en tant que modèle général, je ne suis pas si sûr. Par exemple, lors du chaînage dans Beautiful Soup like team_members = soup.find(class_='section team').find_all('ul').find_all('li'), la valeur de retour de chaque .find(...)appel ne correspond pas encore à la signification de team_members.
Taylor Edmiston
1
@TaylorEdmiston Vous pouvez bien sûr avoir des noms différents pour les résultats partiels. Quelque chose comme section = soup.find(class_='section team')et team_members = section.find_all('ul').find_all('li').
Jeyekomon
4

Selon la référence du langage Python,
vous pouvez utiliser une barre oblique inverse.
Ou tout simplement le casser. Si un crochet n'est pas apparié, python ne le traitera pas comme une ligne. Et dans de telles circonstances, l'indentation des lignes suivantes n'a pas d'importance.

Haozhun
la source
4

C'est une solution un peu différente de celle fournie par d'autres, mais une de mes préférées car elle conduit parfois à une métaprogrammation astucieuse.

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

C'est une technique intéressante pour créer des recherches. Parcourez une liste de conditions à extraire à partir de votre formulaire de requête complexe (ou des déductions basées sur une chaîne sur ce que l'utilisateur recherche), puis éclatez simplement le dictionnaire dans le filtre.

Árni St. Sigurðsson
la source
1

Vous semblez utiliser SQLAlchemy, si c'est vrai, la sqlalchemy.orm.query.Query.filter_by()méthode prend plusieurs arguments de mot-clé, vous pouvez donc écrire comme:

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

Mais ce serait mieux:

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
la source
+1 pour le conseil SQLAlchemy filter_by (). C'est bien pour cet exemple, mais j'utilise souvent filter () à la place qui n'accepte qu'une seule condition.
Juliusz Gonera
1

J'aime mettre en retrait les arguments de deux blocs et l'instruction d'un bloc, comme ceci:

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)
acgtyrant
la source