Values, Names, and Types
What a variable really is in Python — a name attached to a value, not a box holding one — plus the basic types, why the distinction between them matters, and what None is for.
What a variable really is in Python — a name attached to a value, not a box holding one — plus the basic types, why the distinction between them matters, and what None is for.
Someone asked a program how many photos to process, typed 5,
and the program processed 555 of them. No error, no warning, no
crash — just a number that looked entirely plausible and was
wrong by a factor of a hundred.
The cause was three lines of ordinary-looking code, and the fix was one word. Both make complete sense once you know what a Python name actually is, and both are baffling until then.
By the end of this lesson you will know how Python stores information, why the popular mental picture of a variable is wrong in a way that eventually bites, and how to find out what you are actually holding.
Here is the whole mechanism:
photo_count = 400Read it right to left. The value 400 exists. The name
photo_count is attached to it. From now on, writing
photo_count gets you that value.
photo_count = 400
print(photo_count) # 400
print(photo_count + 12) # 412The = is not the equals sign from mathematics. In maths,
x = 5 states a fact. In Python it performs an action: attach
this name to this value. That is why this is not nonsense:
photo_count = 400
photo_count = photo_count + 50
print(photo_count) # 450As a mathematical claim, photo_count = photo_count + 50 is
false for every number. As an instruction it is ordinary: work
out the right-hand side using the value the name currently has,
then attach the name to the answer.
Now the idea that matters, because almost every beginner is taught the wrong picture and has to unlearn it later.
The common picture is a box. photo_count = 400 is described as
putting the number 400 into a box called photo_count. It is a
comfortable image, and it is wrong.
What actually happens is closer to a luggage label. The value sits somewhere in memory. The name is a tag tied to it. Assigning a name does not copy anything and does not create a container — it ties another tag to something that already exists.
Most of the time the difference is invisible. Here is where it stops being invisible:
first = [1, 2, 3] # a list of three numbers
second = first # tie a second label to the same list
second.append(4) # change the thing, through the second label
print(first) # [1, 2, 3, 4]first changed, and you never mentioned first. The two
pictures give completely different answers to why.
Cannot account for this.
You put something into a second box, so why did the contents of the first box change?
There is no answer, because the boxes were never there.
Predicts it exactly.
There was only ever one list. second = first tied a second
tag to it rather than making a copy.
Changing the thing is visible through every label attached to it.
You will meet lists properly in a later lesson. The reason this appears now is that it is a fact about names, not about lists, and learning names correctly the first time is cheaper than learning them twice.
Every value has a type: what kind of thing it is, which decides what you can do with it.
Four of them cover most of what you will write at first.
int is a whole number, and Python's are unusual in having
no upper limit — you can multiply them until you run out of
memory and they stay exact.
float is a number with a fractional part. It is stored as
an approximation, which has consequences surprising enough that
they get their own lesson later.
str is text, short for "string" — a string of characters.
It goes in quotes, single or double, as long as you close it the
way you opened it. The quotes are how Python tells text from
code: photographer is a name to look up, "photographer" is
fourteen characters.
bool holds True or False and nothing else. Both are
capitalised, and both are keywords rather than text — True and
"True" are different things.
Types decide behaviour, which is why the same symbol can do two jobs:
+ between numbers adds. + between text joins. Neither is a
special case — Python asks the values what + means for them.
When something behaves oddly, the cause is very often that a value is not the type you assumed. Ask:
That is the single most common surprise in a beginner's first week, and it is the bug from the top of this lesson. Here it is in full.
input always hands back text
Even when the user typed digits. input("How many? ") with
5 typed gives you the one-character string "5", not the
number 5.
Some operations refuse it, loudly
answer + 10 raises
TypeError: can only concatenate str (not "int") to str.
Annoying, but it tells you exactly what is wrong.
Others accept it and mean something else
answer * 3 is "555". "5" * 3 is a perfectly legal
instruction meaning "repeat this text three times", so Python
does that. No error, and a plausible-looking wrong answer.
Convert as early as you can
answer = int(input("How many photos? ")). Now answer * 3
is 15, and the wrong type never gets far enough to be a
mystery.
The conversions you will reach for:
That last one is worth remembering: int() throws away the
fractional part rather than rounding, so int(3.9) is 3. Use
round() when you want rounding.
Sometimes the honest answer is that there is no value.
None is Python's way of saying "deliberately nothing". It
is not zero, not empty text, not False — it is the absence of
a value, stated explicitly.
It matters because it lets you distinguish two situations that
look alike and are not: a photographer with no middle name
(None, we know there isn't one) and a photographer whose
middle name we have not looked up yet. Using "" for both loses
that difference permanently.
Test for it with is, not ==:
You will read your names far more often than you write them.
Python's convention is lower_case_with_underscores for
variables. Names may contain letters, digits and underscores,
and may not start with a digit.
All three work. Only one tells you what it is in six months.
Bad — names that describe the type instead of the meaning.
Good — names that describe what the value is.
Now imagine a bug on line ninety: print(str1 + num). With the
first version you must scroll back to find out what str1 held
and whether adding num to it was ever sensible. With the
second, photographer + photo_count is visibly wrong on sight —
you cannot add a count to a person's name. Good names turn a
class of bug into something you notice while reading, and this
costs nothing but the seconds it takes to type the longer word.
You can now hold on to information, give it a name, find out what kind of thing it is, and convert between kinds. More importantly you have the correct picture of what a name is — a label tied to a value, not a box containing one — which is the piece that decides whether the behaviour of lists and dictionaries later feels logical or arbitrary.
Next is Strings and Text, which takes the type you will
handle most often and does it properly: building text out of
values without the + and str() dance used here, the handful
of string methods worth memorising, and why text can never be
changed in place once it exists.
Before you move on, go back to the interactive prompt and try to
surprise yourself. Assign a list to two names and change it
through one. Ask type() about everything you can think of,
including type(type). Try int("hello") and read the error.
Five minutes of deliberately breaking things here builds an
intuition that no amount of reading will.
ASSIGNMENT
name = value tie this name to this value
x = x + 1 evaluate the right, then move the label
a = b two labels, ONE value - not a copy
THE FOUR YOU START WITH
int 400 whole number, no size limit
float 3.75 has a fractional part, approximate
str "Ana" text, in matching quotes
bool True/False capitalised, and not text
None deliberately no value at all
FINDING OUT
type(value) what kind of thing is this?
value is None the right way to test for None
CONVERTING
int("42") text to whole number
float("3.5") text to decimal
str(42) number to text
int(3.9) -> 3, truncates, does NOT round
round(3.9) -> 4, when you wanted rounding
TRAPS
input() returns text always, even when digits were typed
"5" * 3 "555", not 15 - and no error
= is not equality it is an instruction, not a claim
do not shadow list, str, type, id, sum, input
NAMING
lower_case_with_underscores
name the meaning, not the typephoto_count = 400 # int - a whole number
average_size = 3.75 # float - a number with a decimal part
photographer = "Ana Duarte" # str - text, in quotes
is_processed = False # bool - True or False, nothing elseprint(3 + 4) # 7 - two ints: addition
print("3" + "4") # 34 - two strs: joining
print("photo" * 3) # photophotophotoanswer = input("How many photos? ")
print(type(answer)) # <class 'str'>int("42") # 42 text to whole number
float("3.5") # 3.5 text to decimal
str(42) # "42" number to text
int(3.9) # 3 decimal to whole - truncates, does not roundmiddle_name = Noneif middle_name is None:
print("No middle name recorded")n = 400
pc = 400
photo_count = 400str1 = "Ana Duarte"
str2 = "Lisbon"
num = 400
list1 = [str1, str2]photographer = "Ana Duarte"
location = "Lisbon"
photo_count = 400
caption_fields = [photographer, location]