29 votes

Comment garder le code Python en dessous de 80 caractères sans le rendre laid ?

C'est une question qui revient sans cesse dans toute ma programmation, en python ou autre. J'aime vraiment garder mon code en dessous de 80 caractères si c'est possible et si ce n'est pas horriblement laid. Dans un langage comme Perl, ce n'est pas trop difficile puisque les espaces blancs n'ont pas d'importance. En Python, où c'est le cas, je me frappe la tête contre le mur plus souvent que je ne le voudrais en essayant de trouver une façon "sympa" de diviser mes longues lignes. Alors, gourous du code, comment faites-vous ? Avez-vous des stratégies générales à me communiquer ?

Un problème particulier auquel je suis confronté en ce moment est le suivant :

self.SomeLongLongName = SomeLongLongName.SomeLongLongName(some_obj, self.user1, self.user2)

Lorsque j'essaie naturellement de couper cela en Python, la seule façon à moitié décente dont je dispose semble être :

self.SomeLongLongName = SomeLongLongName.SomeLongLongName(some_obj,
                                                          self.user1
                                                          self.user2)

Ce n'est pas si mal, je suppose, mais cela prend trois lignes, ce qui est tout à fait inutile. Il doit y avoir un meilleur moyen, non ?

Note : Je sais que certains d'entre vous n'aiment pas les 80 caractères par ligne et ont créé leurs propres limites. Je comprends la motivation derrière cela et je la respecte, mais 80 caractères est ma limite préférée. Ne prenez pas de place en essayant de me convaincre de passer à 120 ou autre ici.

23voto

davidg Points 2994

Votre style de code semble insister sur le fait que si vous interrompez une ligne à l'intérieur d'une parenthèse, les lignes situées en dessous doivent être alignées avec elle :

self.SomeLongLongName = SomeLongLongName.SomeLongLongName(some_obj,
                                                          self.user1
                                                          self.user2)

Si vous êtes prêt à abandonner cette exigence, vous pouvez formater le code de la manière suivante, où les lignes continues ont une double indentation fixe :

self.SomeLongLongName = SomeLongLongName.SomeLongLongName(
        some_obj, self.user1, self.user2)

Cela évite d'écrire du code dans la marge de droite de la page, et c'est très lisible une fois qu'on y est habitué. Elle présente également l'avantage de ne pas avoir à réindenter toutes les lignes suivantes si vous modifiez le nom de "SomeLongLongName". Un exemple plus long serait le suivant :

if SomeLongLongName.SomeLongLongName(
        some_obj, self.user1, self.user2):
    foo()
else:     
    bar()

Le double retrait pour les lignes continues permet de les séparer visuellement des lignes mises en retrait parce qu'elles sont dans un if ou else bloc.

Comme d'autres l'ont noté, l'utilisation de noms abrégés est également utile, mais ce n'est pas toujours possible (par exemple, lors de l'utilisation d'une API externe).

15voto

Johnsyweb Points 45395

La méthode préférée pour envelopper les longues est d'utiliser la continuation de ligne implicite de Python de Python à l'intérieur des parenthèses, des crochets et des accolades. Les longues lignes peuvent être réparties sur plusieurs lignes en en enveloppant les expressions entre parenthèses. Celles-ci doivent être utilisées de préférence à l'utilisation d'une barre oblique inversée pour la ligne. Veillez à indenter la ligne continue de manière appropriée. Le site endroit préféré pour interrompre une ligne autour d'un opérateur binaire est après le site l'opérateur, pas avant.

Guide de style PEP 8 pour le code Python (suivre le lien pour des exemples).

8voto

Michael Kent Points 830
self.SomeLongLongName = SomeLongLongName.\
    SomeLongLongName(some_obj, self.user1, self.user2)

"\" est votre ami. Bien entendu, vous savez déjà que vous pouvez séparer les lignes d'une liste d'arguments par des virgules, sans utiliser '\'. Aussi, si vous avez de longues chaînes de caractères :

myLongString = "This is a really long string that is going to be longer than 80 characters so oh my what do I do to make this work out?"

devient :

myLongString = "This is a really long string that is going to be longer than"\
    " 80 characters so oh my what do I do to make this work out?"

Cela fonctionne parce que Python combinera des chaînes littérales adjacentes, en ignorant les espaces entre les chaînes littérales adjacentes.

4voto

Derek Litz Points 3074

Certaines personnes citaient la classe Rectangle comme un mauvais exemple. Cet exemple dans le pep8 est pas le seul moyen de le faire.

Original :

class Rectangle(Blob):

    def __init__(self, width, height,
                 color='black', emphasis=None, highlight=0):
        if (width == 0 and height == 0 and
            color == 'red' and emphasis == 'strong' or
            highlight > 100):
            raise ValueError("sorry, you lose")
        if width == 0 and height == 0 and (color == 'red' or
                                           emphasis is None):
            raise ValueError("I don't think so -- values are %s, %s" %
                             (width, height))
        Blob.__init__(self, width, height,
                      color, emphasis, highlight)

C'est ainsi que I l'écrirait.

class Rectangle(Blob):

    def __init__(self, width, height, color='black', emphasis=None,
            highlight=0):
        if (width == 0 and height == 0 and color == 'red' and
                emphasis == 'strong' or highlight > 100):
            raise ValueError("sorry, you lose")
        if width == 0 and height == 0 and (color == 'red' or 
                emphasis is None):
            msg = "I don't think so -- values are %s, %s" % (width, height)     
            raise ValueError(msg)
        Blob.__init__(self, width, height, color, emphasis, highlight)

La raison en est que :

  • Une indentation supplémentaire pour s'aligner sur les "(" est une perte de temps si votre éditeur ne le fait pas pour vous, et elle est plus difficile à lire car il y a beaucoup d'espace blanc au début.
  • J'essaie de casser le plus tard possible, à moins qu'il y ait une raison impérieuse dans la logique du code.
  • L'alignement avec le '(' dans ce cas crée exactement le même niveau d'indentation que la ligne suivante... très mauvaise coïncidence ! La double indentation des lignes de continuation résout ce problème.
  • Je préfère l'évitement si la raison de l'utilisation de la continuation de ligne est d'essayer de faire trop de choses sur une seule ligne. L'exemple ici est le ValueError où le formatage est effectué à l'aide de l'opérateur de formatage de chaîne. Je mets msg à la place. (Note : Les chaînes de formatage utilisant la méthode format sont préférées, et % est déprécié depuis la version 3.1).

3voto

steveha Points 24808

Je soutiens la réponse de Michael Kent (et je l'ai votée en haut).

Mais aussi, vous devez lire "PEP 8" et absorber ses leçons.

http://www.python.org/dev/peps/pep-0008/

Mais Python, avec ses espaces de noms, ses fonctionnalités puissantes et ses classes orientées objet, devrait vous permettre d'utiliser des noms courts et pratiques pour les choses.

En C, vous devez utiliser des identifiants longs dans de nombreux cas, car les noms doivent être uniques dans une portée donnée. Ainsi :

char *StringFromInt(int x);
char *StringFromFloat(float x);
char *StringFromUnsigned(unsigned int x);

char *str_temp = strdup(StringFromUnsigned(foo_flags));

En Python, tous ces éléments seraient les fonctions intégrées de la commande str() :

temp = str(foo_flags)

En C++, vous disposez de classes et d'espaces de noms, ce qui vous permet d'utiliser des fonctionnalités orientées objet comme en Python, mais en C, vous avez besoin de noms globalement uniques, ce qui vous oblige souvent à faire ce genre de choses :

typedef struct s_foo
{
   // struct members go here
} FOO;

FooAdd();
FooSubtract();
StringFromFoo();

En Python, vous devez soit ajouter des fonctions membres, soit surcharger les opérateurs, selon le cas :

class Foo(object):
    def __init__(self):
        # member variables initialized here
    def add(self, x):
        # add x to a Foo
    def subtract(self, x):
        # subtract x from a Foo
    def __str___(self):
        # return a string that represents a foo

f = Foo()
f.add(x)
f.sub(y)
# the following two both use __str__()
temp = str(f)
print(f)

Vous pouvez également privilégier les noms de variables très longs à des fins d'auto-documentation. Je préfère la brièveté :

import math

class Circle(object):
    """\
Circle: a class representing a circle in a plane.
Includes the following member functions:
    area() -- return the area of the circle"""
    def __init__(self, center=Point([0, 0]), radius=0.0):
        """\
Circle(center, radius)
center must be an instance of class Point() or convertible to Point()
radius must be an int or float and must not be negative"""
        if radius < 0:
            raise ValueError("radius must be >= 0")
        self.center = Point(center)
        self.radius = float(radius)
    def area(self):
        "returns area as a float."
         return math.pi * self.radius ** 2

c = Circle([23, 45], 0.5)
print(c.area())

class CircleGraphicsObject(object):
    def __init__(self, CenterOfTheCircle, RadiusOfTheCircle):
        # init code goes here
    def AreaOfTheCircle(self):
        return math.pi * self.RadiusOfTheCircle ** 2

CircleInstance = CircleGraphicsObject(PointObject([23, 45]), 0.5)
print(CircleInstance.AreaOfTheCircle())

Je préfère nettement le premier style, laconique, au second. Conformément à la PEP 8, j'aime les noms de variables en minuscules (tels que c pour le Circle instance). En Python, il est aussi généralement recommandé d'utiliser le "Duck Typing" comme je l'ai fait dans la classe terse : si vous voulez que le rayon soit un float, alors il faut le convertir en un float sur __init__() plutôt que de vérifier son type. De même, plutôt que de vérifier si l'on vous a transmis un fichier Point Par exemple, il suffit de contraindre tout ce que vous obtenez à une Point . Vous laissez Point.__init__() soulève une exception si l'argument n'a pas de sens en tant que Point il n'est pas nécessaire de procéder à un enregistrement supplémentaire. Circle.__init__() . En outre, votre Point.__init__() peut vérifier explicitement si vous lui avez passé une instance de Point et retourner l'instance inchangée, s'il est vraiment coûteux d'initier une Point . (Dans cet exemple, un Point n'est en fait qu'une paire de valeurs, donc il est probablement assez rapide de simplement recréer le point et vous n'avez pas besoin de la vérification).

Vous avez peut-être remarqué la façon étrange dont j'ai fait la chaîne de caractères multi-lignes à triple guillemets. En raison des règles d'indentation de Python, j'ai dû indenter la chaîne de caractères entre guillemets, mais je ne veux pas indenter les lignes de la chaîne de caractères car l'indentation ferait partie de la chaîne de caractères. En fait, je veux que toutes les lignes multiples soient dans la marge de gauche, afin de pouvoir voir clairement la longueur de ces lignes (et m'assurer qu'elles font toutes 79 caractères ou moins). J'utilise donc l'échappement backslash pour permettre à la première ligne de la chaîne multi-lignes d'être dans la marge de gauche avec les autres lignes, sans insérer de nouvelle ligne au début de la chaîne multi-lignes.

Quoi qu'il en soit, le style plus court permet de taper plus facilement les noms de variables et autres, et de faire rentrer plus facilement vos lignes dans la limite des 79 colonnes recommandée par PEP 8.

Il ne serait même pas complètement horrible d'utiliser des noms de membres internes d'une seule lettre, dans une classe aussi simple que celle-ci. Avec seulement deux membres, vous pourriez très bien utiliser les noms suivants .c pour l'élément central et .r pour le rayon. Mais cela ne s'adapte pas bien, et .center et .radius sont toujours faciles à taper et à retenir.

C'est aussi une très bonne idée de mettre des docstrings informatifs. Vous pouvez utiliser des noms un peu laconiques, mais donner des explications plus longues dans la docstring.

class Foo(object):
    # init goes here
    def area(self):
        "returns area as a float."
         return self.area

class VerboseFoo(object):
    # init goes here
    def AreaAsFloat(self):
        return self.FloatAreaValue

Les espaces de noms sont excellents. Remarquez comme c'est clair quand on utilise math.pi ; vous savez que c'est la constante mathématique, et vous pourriez avoir une variable locale pi (pour "Program Index" peut-être) et il n'entre pas en collision avec la constante mathématique.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X