By the end you'll know
- The four basic types of value: text, whole numbers, decimals and true or false
- How to store a value under a name, and change it later
- How to do arithmetic, compare values, and combine the answers
- How to build a line of output with an f-string
- How to convert between types, and why a setting that looks like a number arrives as text
Where your checker is
At the end of Lesson 1, checker.py printed two lines you typed by hand. It doesn't know anything about a site: not its name, not what it answered, not how long it took. It can't work anything out either, like whether the site is up.
This lesson teaches it to remember things and to work things out. You'll build it up a few lines at a time, so keep checker.py open in your editor, and keep a terminal running python3 beside it for quick experiments at the >>> prompt.
Four types of value
The checker needs to hold three facts about a site: its name, its status code, and how long it took to answer. Those are different kinds of data, and Python cares about the difference, the same way a form that asks for your age expects 34, not "thirty-four". Every value has a type, which decides what you can do with it: do maths with it, compare it, or only show it. Ask Python for a few types with type():
>>> type("example.com")
<class 'str'>
>>> type(200)
<class 'int'>
>>> type(0.25)
<class 'float'>
>>> type(True)
<class 'bool'>
Python prints each type as <class '...'>. For now, read "class" as "type": <class 'str'> just means "this is a str". Classes are how you make types of your own, which comes much later.
- str (string): text, written inside quotes.
"example.com"and'example.com'are the same. - int (integer): a whole number, like
200or-3. No quotes. - float (floating point number): a number with a decimal point, like
0.25. - bool (Boolean): one of exactly two values,
TrueorFalse, with a capital letter.
The quotes matter more than anything else here. 200 is a number. "200" is text that happens to contain the characters 2, 0 and 0. They look alike on screen, but Python treats them as completely different things, and the last part of this lesson shows where that bites.
There's one more value you'll meet often: None, which means "no value". You'll see it properly in Lesson 4.
Variables: names for values
To keep a value, give it a name. Put a name, an equals sign, and a value, and Python stores the value under that name:
>>> status_code = 200
>>> status_code
200
The = here doesn't mean "equals" as in maths. It means "store the value on the right under the name on the left". You can store a new value under the same name at any time, and the old one is replaced:
>>> status_code = 404
>>> status_code
404
Once a value has a name, every line that uses the name gets the value, so changing it in one place changes it everywhere.
status_code" instead of describing the parcel. Reassigning moves the tag to a different parcel. The name tag doesn't change what's inside; it just tells you which parcel you mean.Now give the checker its three facts. In checker.py, add these lines under the comment, above the two print lines:
site = "example.com"
status_code = 200
response_seconds = 0.25
For now you type these in. In Lesson 6, the checker gets them from a real website. Notice the names:
- They use letters, numbers and underscores, and don't start with a number.
status_codeworks;2nd_sitedoesn't. - They follow Python's convention, snake_case: lowercase words joined by underscores.
- They're case sensitive.
status_codeandStatus_codeare two different names. (The break-it section shows what happens when you mix them up.) - They say what the value is.
response_secondstells the next reader far more thanrorx.
Working things out
The checker has its facts. Now it needs to work two things out: how long the site took in milliseconds, and whether the site is up.
Arithmetic works on ints and floats:
>>> 7 / 2
3.5
>>> 7 // 2
3
>>> 7 % 2
1
>>> 2 ** 10
1024
+, - and * do what you'd expect. / always gives a float, even when the answer is whole. // divides and drops the part after the decimal point, % gives the remainder, and ** raises to a power.
Strings have their own versions of + and *. Adding joins them, and multiplying repeats them:
>>> "web" + "site"
'website'
>>> "-" * 10
'----------'
Comparisons ask a question about two values, and the answer is always a bool. Your banking app does this every time you pay: is the balance at least the amount? Yes or no. In Python:
>>> 200 == 200
True
>>> 404 != 200
True
>>> 150 < 1000
True
Note the double equals: == asks "are these the same?", while a single = stores a value. != means "not equal", and <, >, <= and >= mean less than, greater than, and their "or equal to" versions.
Combining answers uses three words: and (both must be true), or (at least one must be true), and not (flips true to false and back):
>>> 200 >= 200 and 200 < 400
True
>>> not True
False
That first line is exactly the question the checker needs to answer: is the status code 200 or more, and less than 400? Web servers answer with codes in that range when things went well (200 means OK, and most of the 300s mean "go to this other address instead"), and with codes of 400 and above when something went wrong.
Add these two lines to checker.py, below the three facts:
is_up = status_code >= 200 and status_code < 400
response_ms = round(response_seconds * 1000)
is_up stores the answer to the comparison, a bool. response_ms turns seconds into milliseconds, and round() rounds it to a whole number. Both are worked out from the facts above them, so they're always right for whatever facts you give.
f-strings: reporting what it found
Last, the checker needs to report what it found. To show values inside text, put an f straight before the opening quote, and the variable names inside curly brackets:
>>> site = "example.com"
>>> status_code = 200
>>> f"{site} answered {status_code}"
'example.com answered 200'
This is an f-string (formatted string). Python fills in each {name} with that variable's value, the same way a text from your bank fills your name and balance into one template sent to millions of customers. You can also put a calculation inside the brackets, and add a format after a colon. :.2f means "as a decimal with 2 places":
>>> response_seconds = 0.256
>>> f"{response_seconds:.2f} seconds"
'0.26 seconds'
In checker.py, replace the line print("2 sites to check") with these two:
print(f"{site}: {status_code} in {response_ms} ms")
print(f"Up: {is_up}")
Checkpoint: your checker so far
Your checker.py should now look like this:
# A website checker, built one piece at a time.
site = "example.com"
status_code = 200
response_seconds = 0.25
is_up = status_code >= 200 and status_code < 400
response_ms = round(response_seconds * 1000)
print("Checking sites...")
print(f"{site}: {status_code} in {response_ms} ms")
print(f"Up: {is_up}")
The blank lines are optional; they just group the facts, the working out, and the output. Run it:
python3 checker.py
Checking sites...
example.com: 200 in 250 ms
Up: True
Change status_code to 503 (a common "server is overloaded" answer) and run it again. The last line changes to Up: False, and you didn't touch the line that decides it. That's the point of variables: you describe the rule once, and it works for whatever value comes in. Change it back to 200.
Converting between types
Right now you type the checker's facts in yourself, so its numbers really are numbers. That won't always be true. Python won't guess what you meant when you mix types. You convert a value yourself, with the type's name:
>>> int("200")
200
>>> str(200)
'200'
>>> float("0.25")
0.25
>>> int(0.9)
0
int("200") reads the text and gives back the number. str(200) does the reverse. Watch the last one: int() on a float chops off the decimal part rather than rounding, so 0.9 becomes 0. Use round() when you want rounding.
If you've ever had a spreadsheet refuse to add up a column because the numbers were stored as text, you've already met this problem. Most data reaches your script as text. Anything read from a file, typed by a person, or set as an environment variable (a named setting a program reads when it starts, like the ones you passed to containers in the Docker track) arrives as a str, even when it looks like a number. Here's what happens if you forget:
>>> limit = "25"
>>> limit * 2
'2525'
>>> int(limit) * 2
50
Pretend "25" came from a file or a setting. It looks like a number, but the quotes show it's text, and multiplying text repeats it. So instead of 50 you get '2525', with no error at all. That's the most dangerous kind of mistake: one that runs and gives a wrong answer quietly.
Break it on purpose
Make three mistakes in checker.py, one at a time, and put each one back before the next.
Text where a number should be. Change the status code line to status_code = "200", with quotes, as if it had been read from a file. Run it:
Traceback (most recent call last):
File "checker.py", line 6, in <module>
is_up = status_code >= 200 and status_code < 400
^^^^^^^^^^^^^^^^^^
TypeError: '>=' not supported between instances of 'str' and 'int'
Read it from the bottom, as in Lesson 1. A TypeError means an operation was used on a type it doesn't work with: Python can't say whether the text "200" is bigger than the number 200. Notice it blew up on line 6, not on line 3 where the real mistake is. The error appears where the value is used, which is why converting at the moment you read a value, like int(limit) above, is the better habit. Take the quotes away.
Text that isn't a number. Change the line to status_code = int("two hundred"):
Traceback (most recent call last):
File "checker.py", line 3, in <module>
status_code = int("two hundred")
ValueError: invalid literal for int() with base 10: 'two hundred'
This is a different error. A ValueError means the type was right (int() does accept text) but this particular value made no sense. "Invalid literal for int() with base 10" is Python's way of saying "I can't read this as a whole number". Lesson 4 shows how to catch this error instead of crashing. Put 200 back.
A capital letter. On line 6, change the first status_code to Status_code:
Traceback (most recent call last):
File "checker.py", line 6, in <module>
is_up = Status_code >= 200 and status_code < 400
^^^^^^^^^^^
NameError: name 'Status_code' is not defined. Did you mean: 'status_code'?
The same NameError you met with prnt in Lesson 1. To Python, Status_code is a brand new name that was never given a value, because names are case sensitive. Fix the capital, and run it once more to make sure you're back to Up: True.
Where you are now
Your checker holds one site's facts, works out whether the site is up, and reports it. But it only knows about one site: checking a second would mean copying every line and renaming every variable. Lesson 3 fixes that with lists, dictionaries and loops.
Check yourself
What type is "200", written with the quotes?
What does 7 // 2 give?
A setting read from a file gives you limit = "25". What does limit * 2 give?
What type of value does a comparison like status_code >= 200 give back?
You set status = 200, and later write print(Status). Why do you get a NameError?
What does the f do in f"{site}: {status_code}"?
Key terms
- Value: a piece of data, like
"example.com",200orTrue. - Type: the kind of value, which decides what you can do with it. Check it with
type(). - str, int, float, bool: text, whole numbers, decimals, and
TrueorFalse. - None: Python's value for "no value".
- Variable: a name for a value, set with
=. - snake_case: Python's naming style: lowercase words joined by underscores.
- f-string: text starting with
fthat fills in{names}with their values. - Environment variable: a named setting a program reads when it starts. Always text.
- TypeError: an operation used on the wrong type, like comparing text with a number.
- ValueError: the right type, but a value that makes no sense, like
int("two hundred").
Go deeper
- An informal introduction to Python (opens in a new tab) in the official tutorial covers numbers, text and variables with more examples.
- Fancier output formatting (opens in a new tab) covers f-strings and format codes like
:.2f.