How Do I Keep Python Code Under 80 Chars Without Making It Ugly?

后端 未结 7 486
醉梦人生
醉梦人生 2021-01-31 14:27

This is a question that keeps recurring in all of my programming, python and otherwise. I really like to keep my code under 80 chars if at all possible/not horribly ugly. In a l

相关标签:
7条回答
  • 2021-01-31 14:45

    Some people were citing the Rectangle class as a poor example. This example in the pep8 is not the only way to do this.

    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)
    

    This is how I would write it.

    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)
    

    The reason being is:

    • Additional indentation to line up with '(' is a waste of time if your editor isn't doing it for you and harder to read since there is so much leading white space IMO.
    • I try to break as late as possible unless there is a compelling reason in the code logic.
    • Lining up with the '(' in this case created the exact same indentation level as the next line... very bad coincidence! Double indenting continuation lines solves this problem.
    • I prefer avoidance if the reason for having to use line continuation is trying to do too much on one line. The example here is the ValueError where they are formatting using the string format operator. I set msg instead. (Note: Format Strings using the format method are preferred, and % is deprecated since 3.1).
    0 讨论(0)
  • 2021-01-31 14:46

    I second Michael Kent's answer (and I upvoted it).

    But also, you should read "PEP 8" and absorb its lessons.

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

    But Python, with its namespaces, powerful features, and object-oriented classes, should let you use conveniently short names for things.

    In C, you need to use long identifiers in many cases because names need to be unique within a given scope. Thus:

    char *StringFromInt(int x);
    char *StringFromFloat(float x);
    char *StringFromUnsigned(unsigned int x);
    
    char *str_temp = strdup(StringFromUnsigned(foo_flags));
    

    In Python, all of these would be the builtin str():

    temp = str(foo_flags)
    

    In C++ you have classes and name spaces, so you should be able to use object-oriented features as in Python, but in C you need globally unique names, so you often have to do stuff like this:

    typedef struct s_foo
    {
       // struct members go here
    } FOO;
    
    FooAdd();
    FooSubtract();
    StringFromFoo();
    

    In Python, you should either add member functions, or overload operators, as appropriate:

    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)
    

    You may also favor really long variable names for self-documenting purposes. I prefer terseness:

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

    I strongly prefer the first, terse style to the second. As per PEP 8, I like all-lower-case variable names (such as c for the Circle instance). In Python, it is also generally recommended to use "Duck Typing" like I did in the terse class: if you want the radius to be a float, then coerce it to a float in __init__() rather than checking its type. Likewise, rather than checking to see if you were passed a Point instance, just coerce whatever you get to a Point. You are letting Point.__init__() raise an exception if the argument makes no sense as a Point; there is no need for an extra check in Circle.__init__(). Also, your Point.__init__() function can explicitly check to see if you passed it an instance of Point and return the instance unchanged, if it is really expensive to init a Point. (In this example, a Point is really just a pair of values, so it's probably fast enough to just re-create the point and you don't need the check.)

    You might notice the odd way I did the multi-line triple-quoted string. Because of the indenting rules in Python, I needed to indent the triple-quoted string, but I don't want to indent the lines of the string because the indent would be part of the string. Really I want all of the multiple lines to be at the left margin, so I can clearly see how long those lines are getting (and make sure they are all 79 chars or shorter). So I use the backslash escape to allow the first line of the multi-line string to be at the left margin with the other lines, without inserting a newline at the beginning of the multi-line string.

    Anyway, the terser style means your variable names and such are easier to type, and it is easier to fit your lines in the 79-column limit recommended by PEP 8.

    It wouldn't even be completely horrible to use internal member names that are one letter long, in a class as simple as this. With only two members, you could pretty well use .c for the center member and .r for the radius. But that doesn't scale up well, and .center and .radius are still easy to type and easy to remember.

    It's also a very good idea to put informative docstrings. You can use somewhat terse names, but have longer explanations in the 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
    

    Namespaces are great. Notice how clear it is when we use math.pi; you know it is the math constant, and you could have some local variable pi (for "Program Index" perhaps) and it does not collide with the math constant.

    0 讨论(0)
  • 2021-01-31 14:50

    I find myself using more and more intermediate variables, that not only help stay within 80 characters, but make the code more readable by giving things descriptive names like:

    old_name = 'reallylonguglypath/to/current/file.foo'
    new_name = 'evenmoreuglylong/to/new/desination/for/file.foo' 
    os.rename(old_name, new_name)
    

    rather than:

    os.rename("reallylonguglypath/to/current/file.foo",
                  "evenmoreuglylong/to/new/desination/for/file.foo")
    

    You can do this with long module and class names too

    method = SomeLongClassName.SomeLongMethodName
    self.SomeLongLongName = method(some_obj, self.user1, self.user2)
    
    0 讨论(0)
  • 2021-01-31 14:53

    Your code style seems to insist that if you break a line inside a parenthesis, lines below need to line up with it:

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

    If you are willing to drop this requirement, you can format the code as follows, where continued lines have a fixed double indent:

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

    This avoids writing code down the right-hand margin on the page, and is very readable once you are used to it. It also has the benefit that if you modify the name of "SomeLongLongName", you don't have to re-indent all of the following lines. A longer example would be as follows:

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

    The double indent for continued lines allows you to visually separate them from lines indented because they are in an if or else block.

    As others have noted, using shorted names also helps, but this isn't always possible (such as when using an external API).

    0 讨论(0)
  • 2021-01-31 14:59

    Try shortening your names if you have that option. Otherwise you can use the \ character to continue your lines onto the next line (along with other similar constructs such as what you mentioned up above).

    0 讨论(0)
  • 2021-01-31 15:03
    self.SomeLongLongName = SomeLongLongName.\
        SomeLongLongName(some_obj, self.user1, self.user2)
    

    '\' is your friend. Of course, you already know that you can split lines in an argument list at commas, without using '\'. Also, if you have long strings:

    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?"
    

    becomes:

    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?"
    

    This works because Python will combine adjacent string literals, ignoring whitespace between the adjacent literal strings.

    0 讨论(0)
提交回复
热议问题