Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

Tags:

python

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 language like Perl, this isn't too hard since white space doesn't matter. In Python, where it does, I wind up knocking my head against the wall more often than I'd like to be trying to think of a "nice" way of splitting up my long lines. So, Code gurus, how do you do it? Any general strategies you can inform me of?

A particular problem I'm dealing with right now is:

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

When I naturally try to cut this off in Python, the only half decent way available to me seems to be:

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

It doesn't look that bad, I guess, but it takes up three lines, which is just completely unnecessary. There must be a better way, no?

Note: I know there are those of you who don't like 80 chars a line and have created your own limits. I understand the motivation behind this and respect it, but 80 chars is my preferred limit. Please don't take up space trying to convince me to go over to 120 or some such here.

like image 561
Eli Avatar asked Jan 30 '11 04:01

Eli


7 Answers

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

like image 158
davidg Avatar answered Oct 05 '22 10:10

davidg


The preferred way of wrapping long lines is by using Python's implied line continuation inside parentheses, brackets and braces. Long lines can be broken over multiple lines by wrapping expressions in parentheses. These should be used in preference to using a backslash for line continuation. Make sure to indent the continued line appropriately. The preferred place to break around a binary operator is after the operator, not before it.

PEP 8 Style Guide for Python Code (follow link for examples).

like image 43
Johnsyweb Avatar answered Oct 05 '22 12:10

Johnsyweb


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.

like image 32
Michael Kent Avatar answered Oct 05 '22 10:10

Michael Kent


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).
like image 33
Derek Litz Avatar answered Oct 05 '22 12:10

Derek Litz


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

like image 20
Noctis Skytower Avatar answered Oct 05 '22 10:10

Noctis Skytower


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.

like image 38
steveha Avatar answered Oct 05 '22 10:10

steveha


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)
like image 2
crizCraig Avatar answered Oct 05 '22 11:10

crizCraig