-5

From How can I fill out a Python string with spaces?, the accepted answer to pad character to string on the right is to use the str.ljust().

But if we want to pad character to string on the left, we can use str.rjust() and to pad on both we can use str.center(), e.g.

>>> s = 'foobar'
>>> s.ljust(len(s)+1) # pads one space to the right
'foobar '
>>> s.rjust(len(s)+1) # pads one space to the left
' foobar'
>>> s.center(len(s)+2) # pads one space to both left and right
' foobar '

But on the backend, is that really more efficient than simply doing this?:

>>> ' ' + s + ' '
' foobar '
>>> ' '*10 + s + ' '*10 # pads ten spaces to both left and right
'          foobar          '

Is the str.center()/str.ljust()/str.rjust() more readable than the ' ' + s + ' '?

The str functions and the ' ' + s + ' ' doing the different thing at the assembly level as shown in:

>>> import dis
>>> dis.dis(lambda: ' ' + s + ' ')
  1           0 LOAD_CONST               1 (' ')
              3 LOAD_GLOBAL              0 (s)
              6 BINARY_ADD          
              7 LOAD_CONST               1 (' ')
             10 BINARY_ADD          
             11 RETURN_VALUE        
>>> dis.dis(lambda: s.center(len(s)+2))
  1           0 LOAD_GLOBAL              0 (s)
              3 LOAD_ATTR                1 (center)
              6 LOAD_GLOBAL              2 (len)
              9 LOAD_GLOBAL              0 (s)
             12 CALL_FUNCTION            1
             15 LOAD_CONST               1 (2)
             18 BINARY_ADD          
             19 CALL_FUNCTION            1
             22 RETURN_VALUE        

Are there any other methods to do the same thing which is/are more pythonic / efficient?


EDITED

Alternatively, this seems to save one step in the disassembler:

>>> ' {} '.format(s)
' foobar '
>>> dis.dis(lambda: ' {} '.format(s))
  1           0 LOAD_CONST               1 (' {} ')
              3 LOAD_ATTR                0 (format)
              6 LOAD_GLOBAL              1 (s)
              9 CALL_FUNCTION            1
             12 RETURN_VALUE      

So is the saving in the disassembler an improvement in efficiency? Or is it no different from the ' ' + s + ' '?

Community
  • 1
  • 1
alvas
  • 94,813
  • 90
  • 365
  • 641
  • 1
    You're not using `dis.dis` correctly: `dis.dis(lambda: ' ' + s + ' ')`. – Ashwini Chaudhary May 18 '16 at 08:04
  • @AshwiniChaudhary, Thanks for the tip, yeah `dis.dis` takes functions instead of statements =) – alvas May 18 '16 at 08:06
  • 3
    You can use `timeit` to check the difference, on my computer `' ' + s + ' '` is twice faster than `center` or `r/ljust`. – Holt May 18 '16 at 08:12
  • 8
    You have already spent more time typing this up than any choice could ever save... – jonrsharpe May 18 '16 at 08:15
  • @jonrsharpe, I guess it's good to know for "posterity" =) – alvas May 18 '16 at 08:17
  • 2
    ...is it? It seems like the definition of premature optimisation - have you profiled your code? Is this a bottleneck? If you really want to find out, have you considered using `timeit` rather than `dis`? – jonrsharpe May 18 '16 at 08:25
  • @jonsharpe It's not really optimization but just understanding what it's doing on the backend and what other ways to do it. I was looking at the previous question and `str.ljust()` seems like an overkill and I'm not sure whether that's really that readable, hence the question . Actually, this is only done once in my code and i think it has little "effect" on anything ;P – alvas May 18 '16 at 08:44
  • @jonrsharpe, knowing a set of random rules is not good for posterity. What's best for posterity is writing the clearest, simplest, plainest correct code and then, if it's ill-performant, optimizing it. If you have some code that is too slow, make a nice benchmark case and profile it with `cProfile` and view the results with runsnakerun (or a similar procedure with the tools of your choice). Timing arbitrary little snippets is not how you do optimization right -- the people who rely on lots of such rules end up with slower code. – Mike Graham May 20 '16 at 22:54
  • @MikeGraham I agree, and mentioned profiling and bottlenecks; not sure why you wrote that like you're arguing with me. – jonrsharpe May 21 '16 at 07:43
  • @jonrsharpe, I just tagged the wrong person, I meant to direct my comment (which I did not mean in an argumentative way toward anyone) to alvas – Mike Graham May 21 '16 at 16:29
  • @MikeGraham ah ok, not to worry – jonrsharpe May 21 '16 at 16:30
  • 4
    The proper way to use `str.format` *for a single value without surrounding text* is to use the `format()` function. You can adjust to the left and right as well as center with the formatting specification: `format(s, '<10')`, `format(s, '>10')` or `format(s, '^10')`. Or use a variables for the positioning and width with `'{s:{d}{w}}'.format(s=s, w=10, d=' – Martijn Pieters May 21 '16 at 22:31
  • 1
    Most of all, use what is *most maintainable* for your code. If you must optimise for speed, use `timeit` to measure. Bytecode can help you understand what the differences are but they don't tell you what is fastest. – Martijn Pieters May 21 '16 at 22:34

2 Answers2

6

The purpose of these functions (str.ljust, str.rjust and str.center) is not to add a fixed number of chars to start and/or end of a string, but to add the right amount of it, and depending on the string's length.

If you just want to add a fixed number of chars, you won't use these as they are not made for that (it costs you a reading of the string's length) and you'll lose some readablity. You would probably use your own proposal :

' ' * num + s + ' ' * num

or even, if the number is fixed :

' ' + s + ' '

That's way simpler to read and understand, and probably to compute too.

BriceP
  • 508
  • 2
  • 12
2

Justifying text on the right or on the left as well as centering it gets only true meaning when printing strings as being vertically aligned in some column. To get these alignments correct over the whole column, you definitively need to evaluate the length of every string to correctly pad each ot them:

def my_centering(s, width):
    padding = width - len(s)
    left = padding // 2
    right = padding - left
    return ' '*left + s + ' '*right

which, as per my Python 3.4 environment, disassembles as:

2           0 LOAD_FAST                1 (width)
            3 LOAD_GLOBAL              0 (len)
            6 LOAD_FAST                0 (s)
            9 CALL_FUNCTION            1 (1 positional, 0 keyword pair)
           12 BINARY_SUBTRACT
           13 STORE_FAST               2 (padding)

3          16 LOAD_FAST                2 (padding)
           19 LOAD_CONST               1 (2)
           22 BINARY_FLOOR_DIVIDE
           23 STORE_FAST               3 (left)

4          26 LOAD_FAST                2 (padding)
           29 LOAD_FAST                3 (left)
           32 BINARY_SUBTRACT
           33 STORE_FAST               4 (right)

5          36 LOAD_CONST               2 (' ')
           39 LOAD_FAST                3 (left)
           42 BINARY_MULTIPLY
           43 LOAD_FAST                0 (s)
           46 BINARY_ADD
           47 LOAD_CONST               2 (' ')
           50 LOAD_FAST                4 (right)
           53 BINARY_MULTIPLY
           54 BINARY_ADD
           55 RETURN_VALUE

So, whatever some improvement of efficiency in assembler for padding one string, if we are vertically aligning at least two strings let us call any library or built-in functions/methods. They will be far faster :-)

Schmouk
  • 177
  • 6