Search code examples
pythoncoding-stylereadabilitycode-readability

How to cleanly keep below 80-char width with long strings?


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.


Solution

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