We closed the previous lesson with EasyTask calculating remaining days, progress percentages and urgencies, but with two very visible shortcomings: the card was printed at a rate of one line per item, illegible, and all the data was still written inside the code. To register the task of another client you had to open the editor.
This lesson fixes both things, and with that your program crosses an important frontier: it stops being something that gets edited and becomes something that gets used. A program that gets used has two points of contact with the world: the input (the data it receives) and the output (the results it displays). In Python those two points are called input() and print().
You have known print() since module 1, but only in its simplest form. Here you will see it in depth: several arguments, the sep and end parameters, escape characters, and above all f-strings, the tool with which text is composed in modern Python and with which you will at last be able to mix words and variables on a single line, line up columns and display decimals to whatever precision you want. Then input() will arrive, and with it a detail that conditions everything that follows: input() always returns text, even when the user types a number.
Contents
- Input and output: a program's two points of contact
print()with several arguments- The
sepandendparameters - Escape characters:
\n,\tand quotes - f-strings: interpolating variables
- Expressions inside an f-string
- Format specifiers: decimals and alignment
- Other ways of formatting that you will see in other people's code
input(): asking for data from the keyboard- The crucial point:
input()always returns text - EasyTask: the card is asked for and presented
- Common mistakes and tips
- Exercises
- Conclusion
- Input and output: a program's two points of contact
Any program, from the simplest one to a banking system, follows the same three-stage scheme:
flowchart LR
A["INPUT<br/>data coming in"] --> B["PROCESS<br/>calculations and decisions"]
B --> C["OUTPUT<br/>results being shown"]
Until now you have worked in the middle: variables (02-01) and expressions (02-02) are pure process. The input was fixed in the code and the output was rudimentary. We are going to cover both ends.
Data can come in by many routes — keyboard, files, network, sensors — and go out by just as many. In this course we start with the most direct: the keyboard to come in and the screen (specifically, the terminal) to go out. Reading and writing files arrives in Saving Data to Files.
A background warning worth bearing in mind from day one: everything that comes in from outside is suspect. The user may type a number where you expected a name, leave the field empty or write "HIGH" in upper case. Checking that the data is correct is called validating, and it is the subject of the next lesson. Here we deal with receiving it and displaying it.
print() with several arguments
print() with several argumentsSo far you have used print() with a single value. But it accepts several, separated by commas:
Output:
This solves at a stroke the problem we left pending in the previous lesson: remember that "Title: " + title only worked with text, and that "Days: " + 3 gave a TypeError. With commas there is no problem, because print() converts each value to text automatically:
remaining_days = 7
percentage = 37.5
completed = False
due_date = None
print("Remaining days:", remaining_days) # Remaining days: 7
print("Percentage:", percentage) # Percentage: 37.5
print("Completed:", completed) # Completed: False
print("Delivery:", due_date) # Delivery: NoneNumbers, booleans, None: print() displays them all without complaint.
There are two details to observe. First: a space appears between each argument, put there by Python, not by you. That is why "Title:" has no trailing space and the output still looks right. Second: there is no limit on the number of arguments.
Notice the result: the % appears separated from the number because the automatic space is put between all the arguments. That automatic space is convenient but also rigid; the next section explains how to control it.
And a print() with no arguments prints a blank line:
It is cleaner than print(""), which does the same thing.
- The
sep and end parameters
sep and end parametersprint() accepts two named parameters that modify its behaviour.
sep changes the separator placed between the arguments. By default it is a space.
print("high", "medium", "low") # high medium low
print("high", "medium", "low", sep=" / ") # high / medium / low
print("high", "medium", "low", sep="") # highmediumlow
print("2026", "08", "04", sep="-") # 2026-08-04That last example is the typical use: composing a date or a path without repeating the separator by hand.
end changes what is printed at the end, after the last argument. By default it is a line break, "\n"; that is why every print() starts on a new line.
Output, all on a single line:
By putting end="" you are telling print() not to break the line, so the next statement carries on where the previous one left off. It is the mechanism behind progress bars and any output built up in parts.
Combining the two:
| Parameter | Default value | What it controls |
|---|---|---|
sep |
" " (a space) |
What is put between the arguments |
end |
"\n" (line break) |
What is put after the last one |
Named parameters like these are called keyword arguments, and they are studied in depth in Parameters and Return Values. For now it is enough to know that they are written name=value and that they go at the end of the call.
- Escape characters:
\n, \t and quotes
\n, \t and quotesInside a text string, the backslash \ has a special meaning: together with the character that follows it, it forms an escape sequence representing something that cannot be typed directly.
| Escape | Meaning | Typical use |
|---|---|---|
\n |
Line break | Several lines in a single print |
\t |
Tab | Separating columns |
\" |
Literal double quote | Quotes inside text delimited by double quotes |
\' |
Literal single quote | The same with single quotes |
\\ |
Literal backslash | Windows paths |
Line breaks. A single print can produce several lines:
Tabs. \t advances to the next tab stop, which gives a column effect:
It works, but with an important limitation: tab stops are fixed (normally every 8 characters), so if a name is longer than the stop, the column goes out of line. To align properly you use the width specifiers of f-strings, which you will see in section 7.
Quotes inside text. There are three ways of dealing with it:
# 1. Different quotes inside and outside
print("The client said: 'I want it by Friday'")
# 2. Escaping the quotes
print("The client said: \"I want it by Friday\"")
# 3. Single quotes outside, double inside
print('The client said: "I want it by Friday"')The first and the third are the most readable; the second is useful when the text contains both types of quote.
Literal backslash. Since \ starts an escape, to write a backslash you have to double it:
There is also the raw string, which disables escapes: it is marked with an r in front. It is very handy for Windows paths:
And for long, multi-line text, triple quotes preserve the format exactly as you write it:
- f-strings: interpolating variables
We come to the central tool of this lesson. An f-string (formatted string) is a string preceded by the letter f in which you can insert variables between curly braces {}.
assignee = "Luis"
remaining_days = 7
print(f"The task belongs to {assignee} and there are {remaining_days} days left.")Compare the three ways of doing the same thing:
| Form | Code | Problem |
|---|---|---|
| Concatenation | "The task belongs to " + assignee |
Does not accept numbers without converting them |
print arguments |
print("The task belongs to", assignee) |
Automatic spaces that are hard to control |
| f-string | f"The task belongs to {assignee}" |
None: it reads like the result |
The decisive advantage of the f-string is that the code looks like the result. You see the complete sentence and, inside it, in its exact place, the gap the data will fill. And it works with any type, with no conversions:
title = "Sole Bakery logo"
hours = 9.5
completed = False
date = None
print(f"Task: {title}") # Task: Sole Bakery logo
print(f"Hours: {hours}") # Hours: 9.5
print(f"Done: {completed}") # Done: False
print(f"Delivery: {date}") # Delivery: NoneThe f goes right up against the opening quote, and it can be used with single, double or triple quotes. If you forget the f, there is no error: the braces are printed literally, which is a very common slip.
print("There are {remaining_days} days left") # There are {remaining_days} days left <- f missing
print(f"There are {remaining_days} days left") # There are 7 days leftIf you need a literal brace inside an f-string, double it: {{ produces {.
- Expressions inside an f-string
Inside the braces there is room not only for variables: there is room for any expression of the ones you learned in the previous lesson.
hours_spent = 9
estimated_hours = 24
current_day = 12
due_day = 19
priority = "high"
completed = False
print(f"Progress: {hours_spent / estimated_hours * 100} %")
print(f"{due_day - current_day} days left")
print(f"Hours to go: {estimated_hours - hours_spent}")
print(f"Urgent: {priority == 'high' and not completed}")Notice the last line: inside the braces there is a complete logical expression, and in the f-string single quotes have been used for 'high' because the double ones are already taken by the outer string. Mixing the two types of quote is the way to avoid the clash.
That said, a piece of style advice: the fact that you can put complex expressions in does not mean you should. This line is correct but illegible:
It is far better to calculate beforehand, with a name that explains what it is, and limit the f-string to displaying:
progress_percentage = hours_spent / estimated_hours * 100
remaining_days = due_day - current_day
tight_on_time = progress_percentage > 60 and remaining_days < 5
print(f"Tight on time: {tight_on_time}")The practical rule: inside an f-string, short and obvious expressions; everything else, in a named variable.
A useful trick for debugging: if you put an = after the expression, the f-string also displays the code that produced it.
It is extremely handy when you are hunting for why a value is not what you expected.
- Format specifiers: decimals and alignment
Inside the braces you can add, after a colon, a format specifier controlling how the value is presented. The general syntax is {value:format}.
Controlling the decimals
The problem is obvious as soon as you calculate a percentage:
percentage = 37.333333333333336
print(f"Progress: {percentage} %") # Progress: 37.333333333333336 %
print(f"Progress: {percentage:.2f} %") # Progress: 37.33 %
print(f"Progress: {percentage:.1f} %") # Progress: 37.3 %
print(f"Progress: {percentage:.0f} %") # Progress: 37 %.2f means "floating-point format with 2 decimals". It rounds, it does not truncate: 37.336 with .2f gives 37.34.
It is the specifier you will use most. Any quantity calculated with / that is going to be shown to a person should carry it.
Lining up columns
This is where f-strings definitively beat the \t tab. You can set a minimum width and an alignment:
| Specifier | Meaning | Example with "Luis" |
|---|---|---|
{text:<10} |
Aligns to the left within 10 characters | Luis______ |
{text:>10} |
Aligns to the right within 10 characters | ______Luis |
{text:^10} |
Centres within 10 characters | ___Luis___ |
{text:<10} with padding |
{text:.<10} pads with dots |
Luis...... |
(The underscores stand for spaces.)
Let's see it composing a table of the Alba Studio team:
print(f"{'ASSIGNEE':<12}{'TASKS':>8}{'HOURS':>8}")
print("-" * 28)
print(f"{'Marta':<12}{3:>8}{12.5:>8}")
print(f"{'Luis':<12}{5:>8}{27.0:>8}")
print(f"{'Nuria':<12}{2:>8}{9.25:>8}")The text is aligned to the left (<) because that is how words are read; the numbers to the right (>) because that is how units, tens and hundreds line up. It is the convention of any well-made table.
And width and decimals can be combined, putting the width first and the precision afterwards:
print(f"{'Marta':<12}{12.5:>8.2f}")
print(f"{'Luis':<12}{27.0:>8.2f}")
print(f"{'Nuria':<12}{9.25:>8.2f}")Now we are talking: a perfect column, with two decimals in every case.
| Specifier | What it does |
|---|---|
{x:.2f} |
2 decimals |
{x:>8} |
Width 8, right aligned |
{x:<15} |
Width 15, left aligned |
{x:^20} |
Width 20, centred |
{x:>10.2f} |
Width 10, right aligned, 2 decimals |
{x:,} |
Thousands separator: 1250000 → 1,250,000 |
{x:.1%} |
As a percentage: 0.375 → 37.5% |
That last one deserves a note: {0.375:.1%} multiplies by 100 and adds the symbol. If your value is already on a 0-100 scale, use .1f and write the % yourself.
- Other ways of formatting that you will see in other people's code
f-strings have existed since Python 3.6 and are the recommended form today. But you will find code — old tutorials, legacy projects — written with the two earlier forms, and it is worth recognising them.
The .format() method, from Python 3.0 onwards:
assignee = "Luis"
days = 7
print("The task belongs to {} and there are {} days left.".format(assignee, days))
print("The task belongs to {0} and there are {1} days left.".format(assignee, days))
print("Progress: {:.2f} %".format(37.333))The {} gaps are filled in order with the arguments of .format(). The specifiers after the colon are the same as in f-strings, so what you learned in the previous section serves you just as well.
The % operator, inherited from the C language and still present in a lot of old code:
print("The task belongs to %s and there are %d days left." % (assignee, days))
print("Progress: %.2f %%" % 37.333)Here %s is a gap for text, %d for an integer and %.2f for a decimal with two figures. It is the most cryptic of the three and the most error-prone.
| Form | Appearance | When to use it |
|---|---|---|
| f-string | f"Hello {name}" |
Always, in new code |
.format() |
"Hello {}".format(name) |
Only if you maintain old code |
% |
"Hello %s" % name |
Never in new code; recognising it is enough |
Take this away with you: write f-strings, and if you come across the other two, you now know what they do.
input(): asking for data from the keyboard
input(): asking for data from the keyboardinput() is the function that stops the program and waits for the user to type something and press Enter.
That program sits waiting without saying anything, which is a dreadful user experience. That is why input() accepts an argument: the prompt message, which is displayed before waiting.
A run (what the user types is where the cursor is, marked separately here):
How it works step by step:
flowchart TD
A["The program reaches input()"] --> B["It shows the prompt message"]
B --> C["It STOPS and waits"]
C --> D["The user types and presses Enter"]
D --> E["input() returns what was typed<br/>as text"]
E --> F["That text is assigned to the variable"]
Three points of use that make all the difference:
1. End the prompt with a space, or a colon and a space. Without it, what the user types will appear stuck to the message:
name = input("Assignee:") # Assignee:Nuria <- ugly
name = input("Assignee: ") # Assignee: Nuria <- correct2. Say exactly what you expect. A vague prompt produces useless data:
priority = input("Priority: ") # the user does not know what to put
priority = input("Priority (high/medium/low): ") # much better3. The result has to be stored. Writing input("Name: ") without assigning it to anything asks for the data and throws it away. It is a common slip.
And a consequence you notice when running: input() blocks the program. Nothing else happens until the user presses Enter. If your program seems to have "hung", check whether it is waiting for input.
- The crucial point:
input() always returns text
input() always returns textHere is the most important idea of the lesson, and it is worth reading twice:
input()ALWAYS returns a value of typestr, without exception, even when the user types a number.
Check it:
The user has typed 3, on screen you see 3, but what is stored is the text "3", not the number 3. And that has immediate and disconcerting consequences:
days = input("Estimated days: ") # the user types 3
print(days * 2) # 33 <- it repeats the text, it does not multiply!"3" * 2 is string repetition, which you already saw in the previous lesson: it produces "33". It is not a flaw in Python; it is that you are multiplying text.
And if you attempt real arithmetic, the program stops:
It is the same TypeError from the previous lesson, seen now from the other side. Let's sum up what happens depending on what you attempt:
Operation with days = input() (user types 3) |
Result |
|---|---|
print(days) |
Displays 3 — it looks like a number |
type(days) |
<class 'str'> — it is not one |
days + 1 |
TypeError |
days * 2 |
"33" (repetition, not multiplication) |
days > 2 |
TypeError — text cannot be compared with a number |
f"Days: {days}" |
Days: 3 — it works, because it is only being displayed |
Notice the last row: displaying the data works perfectly. The problem only appears when you want to calculate with it.
That is why, in this lesson's application, EasyTask will ask only for data that is text by nature — title, assignee, priority — and will not ask for the estimated days or the hours. As soon as a number is needed, we will have to convert it, and that conversion, together with what happens if the user types "three" instead of "3", is exactly the content of Type Conversion and Validation.
So we leave two loose ends noted down:
input()returns text and sometimes we need numbers.- The user may write
"HIGH"," high "or"urgent", and none of the three matches"high"(that discovery from the desk check in 01-05).
Both are tied up in the next lesson.
- EasyTask: the card is asked for and presented
Version 0.4 of easytask.py. The program asks from the keyboard for the three text items of the task and displays a card lined up in columns with f-strings. The numeric data is still in the code, waiting until we know how to convert it.
# easytask.py - Alba Studio
# Version 0.4: the text data is asked for from the keyboard
# and the card is presented with f-strings
APP_NAME = "EasyTask"
VERSION = "0.4"
STUDIO = "Alba Studio"
WIDTH = 46
HOURS_PER_WORKDAY = 8
# --- Header ---
print("=" * WIDTH)
print(f"{APP_NAME + ' v' + VERSION:^{WIDTH}}")
print(f"{'Task manager for ' + STUDIO:^{WIDTH}}")
print("=" * WIDTH)
print()
# --- INPUT: data typed by the user ---
print("Enter the task details:")
print()
title = input(" Title : ")
description = input(" Description : ")
assignee = input(" Assignee (Marta/Luis/Nuria) : ")
priority = input(" Priority (high/medium/low) : ")
# --- Data we cannot ask for yet (they are numbers) ---
current_day = 12
due_day = 19
estimated_hours = 24
hours_spent = 9
completed = False
# --- PROCESS: calculations with the data ---
remaining_days = due_day - current_day
progress_percentage = hours_spent / estimated_hours * 100
remaining_hours = estimated_hours - hours_spent
remaining_workdays = remaining_hours / HOURS_PER_WORKDAY
is_urgent = priority == "high" and not completed
# --- OUTPUT: aligned card ---
print()
print("=" * WIDTH)
print(f"{'TASK CARD':^{WIDTH}}")
print("=" * WIDTH)
print(f"{'Title':<16}{title:>{WIDTH - 16}}")
print(f"{'Assignee':<16}{assignee:>{WIDTH - 16}}")
print(f"{'Priority':<16}{priority:>{WIDTH - 16}}")
print(f"{'Completed':<16}{str(completed):>{WIDTH - 16}}")
print("-" * WIDTH)
print(f"{'Remaining days':<16}{remaining_days:>{WIDTH - 16}}")
print(f"{'Hours done':<16}{hours_spent:>{WIDTH - 16}.1f}")
print(f"{'Total hours':<16}{estimated_hours:>{WIDTH - 16}.1f}")
print(f"{'Progress':<16}{progress_percentage:>{WIDTH - 18}.1f} %")
print(f"{'Workdays left':<16}{remaining_workdays:>{WIDTH - 16}.2f}")
print("-" * WIDTH)
print(f"{'Urgent':<16}{str(is_urgent):>{WIDTH - 16}}")
print("=" * WIDTH)
print()
print(f"Description: {description}")A complete run, with Marta registering the job for client Vidal:
==============================================
EasyTask v0.4
Task manager for Alba Studio
==============================================
Enter the task details:
Title : Vidal business cards
Description : 300 units, double-sided, recycled paper
Assignee (Marta/Luis/Nuria) : Nuria
Priority (high/medium/low) : high
==============================================
TASK CARD
==============================================
Title Vidal business cards
Assignee Nuria
Priority high
Completed False
----------------------------------------------
Remaining days 7
Hours done 9.0
Total hours 24.0
Progress 37.5 %
Workdays left 1.88
----------------------------------------------
Urgent True
==============================================
Description: 300 units, double-sided, recycled paperLet's go over the techniques used, because the example condenses the whole lesson:
"=" * WIDTHdraws the separators. It is the string repetition from the previous lesson, here with a very practical use.{text:^{WIDTH}}centres the title within the total width. Notice the advanced detail: the width is not written by hand, it is another variable between braces. So, by changingWIDTHon the first line, the whole card readjusts.{'Title':<16}reserves 16 characters for the label, aligned to the left.{title:>{WIDTH - 16}}takes up the rest of the line with the value aligned to the right. Inside the width braces there is an expression,WIDTH - 16, perfectly valid.{progress_percentage:>{WIDTH - 18}.1f} %combines width, alignment and one decimal; the- 18instead of- 16leaves room for the two characters of the%that follow, so the line still measures exactlyWIDTH.str(completed)converts the boolean to text. It is necessary because the<and>alignment specifiers work differently with booleans than with strings; by converting it first, it lines up like any other text. Thestr()function is, precisely, the first thing you will see in the next lesson.
What still does not work
Try this run and look at the result:
The card comes out just as pretty, but at the bottom it says Urgent False. The comparison priority == "high" has failed because the user wrote in upper case. And if they write superurgent, or leave the field blank by pressing Enter, the program does not complain either: it stores the rubbish and carries on.
That is exactly the work outstanding.
Common Mistakes and Tips
Forgetting the f of the f-string. print("There are {days} days left") prints the braces as they are. It does not raise an error, so you have to spot it in the output. If you see braces on screen, the f is missing.
Using double quotes inside an f-string delimited by double quotes. f"It is {x == "high"}" breaks the string. Use single quotes inside: f"It is {x == 'high'}".
Confusing .2f with "two digits". .2f is two decimals, not two figures in total. For the total width you use the number before the dot: {x:8.2f}.
Aligning numbers to the left. It comes out ragged and makes magnitudes hard to compare. Text to the left (<), numbers to the right (>).
Relying on \t to build tables. It works as long as every value is shorter than the tab stop; as soon as one goes over, the column shifts out of line. Use explicit widths.
Not assigning the result of input(). input("Name: ") without variable = in front asks for the data and discards it.
A prompt with no trailing space. input("Name:") makes what is typed stick to the message. Always leave a space.
Assuming that input() returns a number. It is the mistake that defines this lesson: it always returns a str. If you are going to calculate, you will have to convert.
Prompts that do not explain the options. If only three priorities are accepted, write them in the prompt. The cost of a clear prompt is zero and it saves half the wrong entries.
Tip: separate input, process and output. Look at the structure of this lesson's program: first all the input() calls, then all the calculations, and finally all the presentation. It is easier to read, to test and to modify than if they are all mixed together. This separation is a design principle that holds in programs of any size.
Tip: define the width as a constant. WIDTH = 46 instead of repeating 46 on fifteen lines. Changing the card's layout becomes a matter of one line.
Tip: test with awkward data. Run your program and type an extremely long title, or press Enter without writing anything. The card will go out of line or come out empty. Note down what you saw: that is what needs validating.
Exercises
Exercise 1: Predicting the output
Write down exactly what each block prints, paying attention to spaces and line breaks.
# a)
print("Marta", "Luis", "Nuria", sep=" | ")
# b)
print("Task", end=" -> ")
print("completed")
# c)
hours = 7.456
print(f"{hours:.2f}")
print(f"{hours:.0f}")
# d)
name = "Luis"
print(f"[{name:<10}]")
print(f"[{name:>10}]")
print(f"[{name:^10}]")
# e)
print("Line 1\n\tLine 2 tabbed")
# f)
days = 5
print("There are {days} days left")
print(f"There are {days} days left")
# g)
print(f"{'Total':<8}{1250:>10,}")Exercise 2: Alba Studio team card
Write a program team_summary.py that displays this table using only f-strings (no \t at all), with this data already written in variables:
| Person | Pending tasks | Hours spent | Load percentage |
|---|---|---|---|
| Marta | 3 | 12.5 | 0.25 |
| Luis | 5 | 27.0 | 0.45 |
| Nuria | 2 | 9.25 | 0.30 |
Requirements:
- Header centred within a width of 44 characters, with lines of
=above and below. - Names aligned to the left; numbers to the right.
- Hours with one decimal; the load percentage shown as a percentage with one decimal (remember
{x:.1%}). - The total width must be in a constant and used on every line.
Exercise 3: Interactive task registration
Write task_registration.py that:
- Displays a welcome header.
- Asks from the keyboard, with clear prompts: title, client, assignee and priority.
- Displays a card with the four items lined up in two columns.
- At the end, displays on a single line, using an f-string with an expression inside, whether the task is high priority.
- Prints the
type()of thepriorityvariable and explains in a comment what the result means.
Solutions
Solution 1.
a) Marta | Luis | Nuria — the sep replaces the space with " | ".
b)
A single line: the first print does not break because end=" -> ".
c)
.2f rounds to two decimals (7.456 → 7.46) and .0f to zero decimals (7.456 → 7).
d)
Ten characters of width in all three cases; what changes is where the text is placed. When centring with an odd amount of padding left over, the extra gap goes on the right.
e)
\n breaks the line and \t inserts a tab at the start of the second one.
f)
Without the f, the braces are literal text. It is the most common slip.
g) Total 1,250 — Total takes up 8 characters on the left, and 1250 is displayed with a thousands separator within 10 characters on the right.
Solution 2.
# team_summary.py - Alba Studio
WIDTH = 44
marta_tasks = 3
marta_hours = 12.5
marta_load = 0.25
luis_tasks = 5
luis_hours = 27.0
luis_load = 0.45
nuria_tasks = 2
nuria_hours = 9.25
nuria_load = 0.30
print("=" * WIDTH)
print(f"{'TEAM SUMMARY - Alba Studio':^{WIDTH}}")
print("=" * WIDTH)
print(f"{'PERSON':<14}{'TASKS':>8}{'HOURS':>11}{'LOAD':>11}")
print("-" * WIDTH)
print(f"{'Marta':<14}{marta_tasks:>8}{marta_hours:>11.1f}{marta_load:>11.1%}")
print(f"{'Luis':<14}{luis_tasks:>8}{luis_hours:>11.1f}{luis_load:>11.1%}")
print(f"{'Nuria':<14}{nuria_tasks:>8}{nuria_hours:>11.1f}{nuria_load:>11.1%}")
print("=" * WIDTH)Output:
============================================
TEAM SUMMARY - Alba Studio
============================================
PERSON TASKS HOURS LOAD
--------------------------------------------
Marta 3 12.5 25.0%
Luis 5 27.0 45.0%
Nuria 2 9.2 30.0%
============================================Notice the widths chosen: 14 + 8 + 11 + 11 = 44, exactly WIDTH. When the columns add up to the total width, the lines of = and - fit the table and the result looks deliberate, which is just what we are after.
Observe too that 9.25 with .1f is displayed as 9.2 and not as 9.3: faced with an exact tie, Python rounds to the nearest even digit (so-called banker's rounding). It is not an error, but it is worth knowing when you present figures to a client.
Solution 3.
# task_registration.py - Alba Studio
WIDTH = 44
print("=" * WIDTH)
print(f"{'NEW TASK REGISTRATION':^{WIDTH}}")
print(f"{'Alba Studio':^{WIDTH}}")
print("=" * WIDTH)
print()
title = input("Task title : ")
client = input("Client : ")
assignee = input("Assignee (Marta/Luis/Nuria) : ")
priority = input("Priority (high/medium/low) : ")
print()
print("-" * WIDTH)
print(f"{'CARD':^{WIDTH}}")
print("-" * WIDTH)
print(f"{'Title':<14}{title:>{WIDTH - 14}}")
print(f"{'Client':<14}{client:>{WIDTH - 14}}")
print(f"{'Assignee':<14}{assignee:>{WIDTH - 14}}")
print(f"{'Priority':<14}{priority:>{WIDTH - 14}}")
print("-" * WIDTH)
print(f"High priority: {priority == 'high'}")
# <class 'str'>: input() ALWAYS returns text, even when the user
# types a number. That is why the comparison above only works
# if they write exactly "high" in lower case.
print(type(priority))Example run:
============================================
NEW TASK REGISTRATION
Alba Studio
============================================
Task title : Book fair poster
Client : Booksellers association
Assignee (Marta/Luis/Nuria) : Nuria
Priority (high/medium/low) : medium
--------------------------------------------
CARD
--------------------------------------------
Title Book fair poster
Client Booksellers association
Assignee Nuria
Priority medium
--------------------------------------------
High priority: False
<class 'str'>Try running it again writing HIGH for the priority: the card will come out just as correct, but High priority will still say False. That is the problem we solve next.
Conclusion
Your program now talks to the world. You know that print() accepts several arguments separated by commas — converting each one to text automatically — and that its sep and end parameters control what goes between the values and what goes at the end. You handle the escape characters \n, \t, \" and \\, and you know how to deal with quotes inside text in three different ways.
Above all, you have mastered f-strings: interpolating variables and expressions between braces, rounding decimals with .2f, and lining up columns with <, > and ^ to produce readable tables without depending on the tab. You also recognise .format() and the old % when you meet them in other people's code, even though you always write f-strings.
And you know input(): how it stops the program, how to give it a useful message and — the point that governs the next lesson — that it always returns a str, even when the user types a number.
EasyTask is at version 0.4 and already looks like a tool: Marta runs it, types the title, the description, the assignee and the priority of the job, and gets a lined-up card with the deadline and progress calculations. Nobody has to open the editor to register a new task.
But we have left two very visible loose ends. The first: the numeric data — estimated days, hours spent — is still written in the code, because what comes from input() is text and "3" + 1 gives a TypeError. The second: the program swallows anything the user types at it. If Marta writes HIGH, the comparison fails; if she writes superurgent, it is stored as it is; if she presses Enter without writing, the task is left with no title.
Both are the same problem seen in two ways: data coming from outside cannot be trusted. In Type Conversion and Validation you will learn to convert it to the right type with int(), float() and str(), to clean it up with .strip() and .lower() — which will at last make "HIGH" worth as much as "high", the loose end we left in lesson 01-05 — and to check that it is valid before using it. With that we will close module 2 and EasyTask will be, for the first time, a robust program.
Fundamentals of Programming
Module 1: Introduction to Programming
- What is programming?
- History of programming
- Programming languages
- Development environments
- From problem to algorithm
Module 2: Core Concepts
- Variables and data types
- Operators and expressions
- Input and output
- Type conversion and data validation
Module 3: Control Structures
Module 4: Functions and Procedures
- Defining and using functions
- Parameters and return values
- Variable scope
- Breaking a program down into functions
- Functions as values: lambda and higher order
Module 5: Data Structures
- Lists and arrays
- Strings
- Dictionaries and sets
- Tuples and nested structures
- Saving data to files: text, CSV and JSON
Module 6: Basic Algorithms
Module 7: Objects and Code Organisation
- From data to objects: classes and instances
- Attributes, methods and the constructor
- Collections of objects
- Modules, packages and imports
Module 8: Good Practices and Tools
- Documentation and comments
- Debugging and error handling
- Version control
- Automated testing
- Style, readability and refactoring
