Mark Brown Tuition
Physics, Mathematics and Computer
Science Tuition & Resources
Fancy Printing With Python
Posted on 10-11-17 in Computing
Review of the basics
The first program we all ever write is simply
print("Hello World!")
Most of the time this is good enough. If we want we can pass multiple things to our print function including variables
name = "Mark" number_of_apples = 9 print("Hello", name, "I hope you are well! Apparently you have", number_of_apples, "apples")
If we want to write more complex print statements we should use formatting. In Python there are now three ways of doing this. In this article I'm going to focus on the modern approach using F Strings. NOTE! This will only work on Python3.6 and later.
For example let's rewrite the above with the format
name = "Mark" number_of_apples = 9 some_text = f"Hello {name} I hope you are well! Apparently you have {number_of_apples} apples." print(some_text)
Here Python will replace every instance of {} with the corresponding item inside the format brackets. We can pass any valid Python in these arguments such as
a = 5 b = 6 print(f'{a} x {b} = {a*b}')
Importantly we can also specify arguments inside each pair of curly braces. For instance
details = (('roger', 20), ('mark', 31), ('jeff', 5), ('mary', 101)) for name, age in details: print(f"{name:20} is {age:3} years old")
will output
>>> roger is 20 years old mark is 31 years old jeff is 5 years old mary is 101 years old
Notice how whitespace has been preserved giving everything a rather tidy look! Here we specify the minimum whitespace to give by using a colon followed by a number. Consider another example
some_number = 122.3334444455 print(f"You have £{some_number:.2f}" # For example, if it's money
will output
>>> You have £122.33
For example if we want to print a number out in different bases we can simply use
number = 65 format_options = (('Float', 'f'), ('Decimal', 'd'), ('Binary', 'b'), ('Octal', 'o'), ('Hex', 'x'), ('ASCII', 'c')) for name, mode in format_options: print(f"{name:10} {number:{mode}}")
will give
>>> Float 65.000000 Decimal 65 Binary 1000001 Octal 101 Hex 41 ASCII A
aside Why do you think we have used curly braces inside other curly braces above?
As you can see this becomes rather useful if we know what format code to use! To understand more format patterns have a look at.
Using Format to simplify our code
When we want a lot of information from a user we can often find ourselves writing an input again and again. Consider asking for information about a parcel. We will store our data in a dictionary. We can just do the following
parcel_id = int(input("please enter the parcel id >> ")) length = float(input("Please enter the length >> ")) width = float(input("Please enter the width >> ")) height = float(input("Please enter the height >> ")) weight = float(input("Please enter the weight >> ")) new_parcel = {'id' : parcel_id, 'length' : length, 'width' : width, 'height' : height, 'weight' : weight}
Just by looking we can see this is very repetitive. What we want to to do is share the bits of the code which repeat and insert the changes as needed.
parcel_format = (('parcel id', int), ('length' , float), ('width' , float), ('height' , float), ('weight' , float), ('address' , str)) new_parcel = {} for key, field_type in parcel_format: question_to_ask = f"Please enter the {key} >> " new_parcel[key] = field_type(input(question_to_ask))
Here we have used the format to alter the question being asked each time. Note that the field_type is also changing per data type. Combine this format with input validation will allow us to produce clean code to ask user for data. See my post on asking for validated input for further information.
One possible implementation using validated input is given below
def ask_for_input(key, field_type, lower, upper): """ Asks for valid input from user Ensures value is within inclusive range of lower and upper if lower==upper then no range needed """ while True: question_to_ask = f"Please enter the {key} >> " try: answer = field_type(input(question_to_ask)) except ValueError as e: print(f"{e} - Invalid format for {key}") continue if lower == upper or answer >= lower and answer <= upper: return answer else: print(f"{answer:10} is outside range {lower:3} to {upper:3} for {key}") parcel_format = (('parcel id', int, 0, 1000), ('length' , float, 0, 100), ('width' , float, 0, 100), ('height' , float, 0, 100), ('weight' , float, 0, 100), ('address' , str, 0, 0)) new_parcel = {key : ask_for_input(key, *args) for key, *args in parcel_format}
Here I've added a lower and upper limit to each piece of data we wish to ask for. Note that the final line makes use of dictionary comprehensions and argument unpacking. Here's some example input
>>> Please enter the parcel id >> -1 -1 is outside range 0 to 1000 for parcel id Please enter the parcel id >> apple invalid literal for int() with base 10: 'apple' - Invalid format for parcel id Please enter the parcel id >> 1 Please enter the length >> 20 Please enter the width >> 25 Please enter the height >> 30 Please enter the weight >> 200 200.0 is outside range 0 to 100 for weight Please enter the weight >> 1 Fleet St, London could not convert string to float: '1 Fleet St, London' - Invalid format for weight Please enter the weight >> 80 Please enter the address >> 1 Fleet St, London
This gives us a single dictionary entry
{'parcel id': 1, 'length': 20.0, 'width': 25.0, 'height': 30.0, 'weight': 80.0, 'address': '1 Fleet St, London'}
Extension
- Extent the above code to ask for multiple parcels. Reject existing ids.
- Label parcels based on their density (< 1000 will float, > 1000 will sink).
Summary
F-strings are very powerful and allow us to produce clean output quickly.