14

To begin with, I am working to a get a desired output like this:

*********************************************************************
                                 hello
********************************************************************* 

To achieve this I have assigned the desired output to a variable with multiline string and printing the same with format.

$ cat varibale.py 
decorator = """ **********************************************************************
                               {}
            ********************************************************************** """
print(decorator.format("hello"))

Output:

**********************************************************************
                               hello
            **********************************************************************

The issue with above approach is the extra spaces in the third line of output which is looking odd.

I am able to achieve this in the following way:

$ cat varibale.py 
decorator = """ **********************************************************************
                             {}
********************************************************************* 
"""
print(decorator.format("hello"))

Output:

 **********************************************************************
                             hello
********************************************************************* 

But this way my code doesn't look good, as it is not following the indentation.

Please suggest the right way to achieve the desired output.

0

4 Answers 4

13

One way to make multi-line literal strings look good is to use a backslash to escape the newline, like this:

s = '''\
*********************************************************************
                                 hello
*********************************************************************
'''
print(s)

output

*********************************************************************
                                 hello
*********************************************************************

However, PEP-008 discourages backslash usage like that. It's too fragile: if there's a space between the backslash and the newline then the newline won't get escaped, and the backslash will get printed.

A more versatile approach is to use a function which calculates the amount of padding required to centre the text, and applies it via a nested formatting specifier. For example:

def banner(s, width=69):
    stars = '*' * width
    pad = (width + len(s)) // 2
    return '{0}\n{1:>{2}}\n{0}'.format(stars, s, pad)

print(banner('hello'))
print(banner('Hello, world', width=16))

output

*********************************************************************
                                hello
*********************************************************************
****************
  Hello, world
****************

How it works

That format string is a little dense, so I guess I should try to explain it. ;) For full information on this topic please see Format String Syntax in the docs. The explanation below borrows from & paraphrases those docs.

'{0}\n{1:>{2}}\n{0}'.format(stars, s, pad)

The stuff enclosed in {} in a format string is called a "replacement field". The first item in a replacement field is the optional field name. This lets us identify which arg of .format goes with this replacement field. There are a couple of possible variations for field names, this format string uses numeric names, so it identifies the .format args by their position. That is, 0 corresponds to stars, 1 corresponds to s and 2 corresponds to pad.

If no field names are given they get automatically filled by the numbers 0, 1, 2, ... etc (unless you're using Python 2.6, where field names are mandatory). That's quite useful most of the time, so most format strings don't bother using field names.

After the field name we can give a "format specifier" or "format spec" which describes how the value is to be presented. A colon : separates the field name from the format spec. If you don't supply a format spec then you get a default one, and most of the time that's adequate. But here we do want a little more control, so we need to supply a format spec.

In a form spec the > sign forces the field to be right-aligned within the available space. After the alignment sign we can provide a number to specify the minimum field width; the field will automatically be made larger if necessary.

For example, '{0:>6}'.format('test') says to put argument 0 ('test') in a space that's at least 6 chars wide, aligned to the right. Which results in the string ' test'.

But a format spec can actually contain a whole new replacement field! This allows us to supply a variable to control the field width. So in my format string {1:>{2}} says to put arg 1 here (s), right aligned in a field with a width given by arg 2 (pad). Only one level of replacement field nesting is permitted, but it's hard to think of a situation where you'd actually want deeper nesting.

So putting it all together: '{0}\n{1:>{2}}\n{0}' tells .format to build a string that starts with arg 0 (stars) using the default format spec, followed by a newline, followed by arg 1 (s) right aligned in a field of width pad, followed by another newline, finally followed by arg 0 (stars) again.

I hope that made enough sense. :)


In Python 3.6+, we could use an f-string:

def banner(s, width=69):
    stars = '*' * width
    pad = (width + len(s)) // 2
    return f'{stars}\n{s:>{pad}}\n{stars}'
Sign up to request clarification or add additional context in comments.

1 Comment

could you please explain the logic used here " {0}\n{1:>{2}}\n{0}"
8

you could proceed for example as:

print('*'*80)
print('{msg:^80s}'.format(msg = 'HELLO')) #^ centers the message
print('*'*80)

or if you want to have the text-width dynamic:

def fn(msg, w = 80):
    delim = '*'*w
    fmt = '{msg:^%ds}'%w

    print(delim)
    print(fmt.format(msg=msg))
    print(delim)

fn('hello')

or slightly generalized version should you need to write to a file:

import sys

def fn(msg, w = 80, F = sys.stdout):
    delim = '*'*w
    fmt = '{delim:s}\n{msg:^%ds}\n{delim:s}\n'%w
    F.write(fmt.format(delim = delim, msg = msg))

fn('hello')

4 Comments

Neat and simple. I like it.
first solution won't be good if I need to write the same to a file.
@MatiasCicero thanks for the code correction, missed that one... :)
@Here_2_learn The idea is the same. Just change the print statements to a f.write and add a line break at the end
0

Maybe :

print '*' * 80 + '\n' + ' ' * 38 + 'hello' + '\n' + '*' *80

OR If it is python3

a = lambda x,c,mess: print(c*x + ('\n' if not mess else mess))
a(80, '*', None)
a(38, ' ', 'Hello')
a(80, '*', None)

Comments

0

Starting with Python 3.6, formatting can be done within formatted string literals which can also span multiple lines. Formatting arguments such as width are normally added as integer literals; to use an arbitrary integer expression instead, enclose it in curly braces.

In the given case, this

print(f"""{'*'*width}
{'hello':^{width}}
{'*'*width}""")

might look much more confusing than

print(f"{'*'*width}\n{'hello':^{width}}\n{'*'*width}")

But when it comes to generating code dynamically, you can achieve correct indentation and good readability much more easily by using multi-line formatted string literals.

def gen_recursion(name, op, neutral): return f"""
def {name}(n):
    if n > {neutral}:
        return n {op} {name}(n-1)
    else:
        return {neutral}
"""

samples = (
    ('gauss', '+', 0),
    ('factorial', '*', 1)
)
for (name, op, neutral) in samples:
    s = gen_recursion(name, op, neutral)
    print(s)
    exec(s, globals())
    s = f"{name}(5)"
    print(f"{s} -> {eval(s)}")

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.