22

I'm attempting to keep my code to 80 chars or less nowadays as I think it looks more aesthetically pleasing, for the most part. Sometimes, though, the code ends up looking worse if I have to put line breaks in weird places.

One thing I haven't figured out how to handle very nicely yet is long strings. For example:

#0.........1........2........3........4.........5.........6.........7.........8xxxxxxxxx9xxxxxx
def foo():
    if conditional():
        logger.info("<Conditional's meaning> happened, so we're not setting up the interface.")
        return

    #.....

It's over! Putting it on the next line won't help either:

#0.........1........2........3........4.........5.........6.........7.........8xxxxxxxxx9xxxxxx
def foo():
    if conditional():
        logger.info(
            "<Conditional's meaning> happened, so we're not setting up the interface.")
        return

    #.....

I could use line breaks but that looks awful:

#0.........1........2........3........4.........5.........6.........7.........8
def foo():
    if conditional():
        logger.info(
            "<Conditional's meaning> happened, so we're not setting \
up the interface.")
        return

    #.....

What to do? Shortening the string is one option but I don't want the readability of my messages to be affected by something as arbitrary as how many indentation levels the code happened to have at that point.

Claudiu
  • 224,032
  • 165
  • 485
  • 680

1 Answers1

38

You can split the string into two:

def foo():
    if conditional():
        logger.info("<Conditional's meaning> happened, so we're not "
                    "setting up the interface.")

Multiple consecutive strings within the same expression are automatically concatenated into one, at compile time:

>>> def foo():
...     if conditional():
...         logger.info("<Conditional's meaning> happened, so we're not "
...                     "setting up the interface.")
... 
>>> import dis
>>> dis.dis(foo)
  2           0 LOAD_GLOBAL              0 (conditional)
              3 CALL_FUNCTION            0
              6 POP_JUMP_IF_FALSE       25

  3           9 LOAD_GLOBAL              1 (logger)
             12 LOAD_ATTR                2 (info)
             15 LOAD_CONST               1 ("<Conditional's meaning> happened, so we're not setting up the interface.")
             18 CALL_FUNCTION            1
             21 POP_TOP             
             22 JUMP_FORWARD             0 (to 25)
        >>   25 LOAD_CONST               0 (None)
             28 RETURN_VALUE        

Note the LOAD_CONST for line 3, the bytecode for the function contains one string, already concatenated.

If you were to add a + to the expression, two separate constants are created:

>>> def foo():
...     if conditional():
...         logger.info("<Conditional's meaning> happened, so we're not " + 
...                     "setting up the interface.")
... 
>>> dis.dis(foo)
  2           0 LOAD_GLOBAL              0 (conditional)
              3 CALL_FUNCTION            0
              6 POP_JUMP_IF_FALSE       29

  3           9 LOAD_GLOBAL              1 (logger)
             12 LOAD_ATTR                2 (info)
             15 LOAD_CONST               1 ("<Conditional's meaning> happened, so we're not ")

  4          18 LOAD_CONST               2 ('setting up the interface.')
             21 BINARY_ADD          
             22 CALL_FUNCTION            1
             25 POP_TOP             
             26 JUMP_FORWARD             0 (to 29)
        >>   29 LOAD_CONST               0 (None)
             32 RETURN_VALUE        

Python does fold binary operations on constants at compile time (so +, *, - etc.), in the peephole optimizations for the byte compiler. So for certain string concatenations the compiler may also replace + string concatenation of constants with the concatenated result. See peephole.c, for sequences (including strings) this optimization is only applied if the result is limited to 20 items (characters) or fewer.

Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
  • While there is automatic merging, I still prefer adding a `+` for clarity. Anyway, +1. – orlp Mar 27 '13 at 16:31
  • ah nice, this just might be it. Out of curiosity is that done at parse time or runtime? – Claudiu Mar 27 '13 at 16:31
  • 4
    @nightcracker: The *compiler* merges the strings. With `+` you move the concatenation to run-time. – Martijn Pieters Mar 27 '13 at 16:32
  • @MartijnPieters: ah yes, compiler indeed. – Claudiu Mar 27 '13 at 16:33
  • 1
    @nightcracker: Evidence added. Sorry, but you are wrong. With `+` there are *two* string constants, and they are added at run time. – Martijn Pieters Mar 27 '13 at 16:34
  • 1
    @MartijnPieters counter-evidence: https://gist.github.com/nightcracker/5255760 They're equally fast. – orlp Mar 27 '13 at 16:34
  • @nightcracker: ah nice. i was wondering whether the compiler would be smart enough to handle adding constant strings together. not sure about better readability, though, don't you have to add `+ \` at the line break to make it work? that seems less nice, although more explicit, true. – Claudiu Mar 27 '13 at 16:35
  • 1
    @nightcracker: It seems that for *some* strings the compiler optimizes the string concatenation. For *this* case, the compile-time concatenation *does not happen*. I added a counter-counter example to your gist. – Martijn Pieters Mar 27 '13 at 16:37
  • 1
    @MartijnPieters ah ok. Either way, the performance argument is irrelevant either way IMO - unless we're talking about a performance bottleneck here. But I strongly doubt that this logger entry is a performance bottleneck. – orlp Mar 27 '13 at 16:40
  • 1
    @nightcracker: Probably not. I must say I didn't expect the short-string-with-add-operator concatenation to happen, that was a surprise. – Martijn Pieters Mar 27 '13 at 16:44
  • interesting stuff, for sure. it seems it's an optional optimization a python compiler can make. the string literal concatenation behavior is defined in the [Python Language Reference](http://docs.python.org/2/reference/lexical_analysis.html#string-literal-concatenation) to happen at compile-time, though. – Claudiu Mar 27 '13 at 16:48
  • @Claudiu: Yeah, I found the code that executes the concatenation of string constants, it applies to any binary operator involving two constants, within a size limit. – Martijn Pieters Mar 27 '13 at 16:56
  • @nightcracker: Found the peephole optimization, it won't apply to results over 20 characters long. – Martijn Pieters Mar 27 '13 at 17:00
  • Interesting thing that Victor Stinner sent an e-mail to pyhton-dev today which is related to the subject here: http://mail.python.org/pipermail/python-dev/2013-March/124954.html – jsbueno Mar 27 '13 at 17:16