Python has a lot of types. You will use five of them in almost every program, and this post covers those five plus the handful of traps that catch everyone once.
The five you actually use
count = 12 # int — whole number
price = 15.50 # float — decimal number
name = "Margherita" # str — text
in_stock = True # bool — True or False
discount = None # None — deliberately no value
print(type(price)) # Output: <class 'float'>
Notice there is no declaration. You do not write the type — assigning the value is what creates the
variable, and type() will tell you what you got.
A variable in Python is a name pointing at a value, not a box holding one. That distinction sounds academic until you hit the mutability section below, where it explains everything.
Numbers, and the division trap
int and float behave as you would expect, with one exception that bites
everybody:
print(7 + 3) # Output: 10
print(7 / 2) # Output: 3.5
print(7 // 2) # Output: 3
print(7 % 2) # Output: 1
print(2 ** 10) # Output: 1024
/ always produces a float, even when it divides evenly —
6 / 2 is 3.0, not 3. Use // when you want whole
numbers. If you have come from Java or C, this is the reverse of what you are used to, and it is one
of the differences between Python 2 and 3 that still confuses search results.
Python's ints have no maximum. There is no overflow and no long:
print(2 ** 100) # Output: 1267650600228229401496703205376
Floats are not exact — never use them for money
This is not a Python flaw. Binary floating point cannot represent 0.1 exactly, in any language:
print(0.1 + 0.2) # Output: 0.30000000000000004
print(0.1 + 0.2 == 0.3) # Output: False
For anything involving currency, use Decimal — and build it from a
string, because a float is already wrong before Decimal sees it:
from decimal import Decimal
print(Decimal("0.1") + Decimal("0.2")) # Output: 0.3
print(Decimal(0.1)) # Output: 0.1000000000000000055511151231257827021181583404541015625
The second line is the whole rule in one output: Decimal(0.1) inherits the float's
error. Always quote the number.
Booleans and what counts as false
True and False are capitalised. More importantly, every value can be used
where a boolean is expected, and Python has a specific idea of which ones are false:
falsy = [False, None, 0, 0.0, "", [], {}, ()]
for value in falsy:
if value:
print("never printed")
print("all of those are falsy") # Output: all of those are falsy
Empty containers, zero, the empty string, and None are false. Everything else is true.
This is why if items: is the idiomatic way to ask "is this list non-empty" rather than
if len(items) > 0:.
The trap is that it makes zero and empty indistinguishable from missing. If 0 is a
legitimate value, test for None explicitly.
None, and why is is the right operator for it
None means "no value", and it is what a function returns when it does not return
anything.
def find(name):
return None
result = find("nobody")
print(result is None) # Output: True
print(result == None) # Output: True
Both print True — the second one works, and you should still not write it. Why? == asks "are these equal", which a class
can redefine to mean anything. is asks "are these the same object", which nothing can
redefine. There is exactly one None in a running program, so is None is both
faster and impossible to fool. Same for is not None.
Converting between types
Python will not convert for you, so you do it explicitly. This matters most with
input(), which always hands you a string:
quantity = int("12")
print(quantity + 1) # Output: 13
print(float("15.50")) # Output: 15.5
print(str(99)) # Output: 99
print(int(9.99)) # Output: 9
int() on a float truncates rather than rounding —
int(9.99) is 9. Use round() if you meant to round.
A conversion that cannot work raises rather than guessing:
int("twelve")
That is a ValueError, and handling it
is how you validate user input.
Asking what type something is
type() tells you exactly what a value is, which is useful when debugging. For a test
in real code, use isinstance() instead — it accepts subclasses, which is almost always
what you want:
value = 42
print(isinstance(value, int)) # Output: True
print(isinstance(value, (int, float))) # Output: True
print(isinstance(True, int)) # Output: True
That last line is not a bug. bool is a subclass of int in Python, and
True really does equal 1. It is a historical wart, it very occasionally
matters, and it is worth knowing before it surprises you.
Reaching for isinstance constantly is usually a sign you want
polymorphism instead — Python's habit is to call the method and let
it fail if the object cannot do the job, rather than interrogating the type first.
Mutable and immutable
This is the one that explains the strangest bugs. Some types can be changed in place; some cannot.
- Immutable —
int,float,str,bool,tuple. Operations return a new value. - Mutable —
list,dict,set. Operations change the thing itself.
name = "pizza"
name.upper()
print(name) # Output: pizza
toppings = ["cheese"]
toppings.append("basil")
print(toppings) # Output: ['cheese', 'basil']
upper() returned a new string and name never moved; append() changed the list itself. Because a variable is a name pointing at a value, two names can point at the same mutable object —
and then changing it through one is visible through the other:
a = ["cheese"]
b = a # not a copy — the same list, under a second name
b.append("basil")
print(a) # Output: ['cheese', 'basil']
print(a is b) # Output: True
When you want an actual copy, ask for one: b = a.copy() or b = list(a).
This is the single most common source of "why did that change" in Python, and
Lists returns to it.
Next
String Methods covers the type you will handle more than any other.