Python – Data Types

July 4, 20265 min readUpdated 8/20/2026

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.

  • Immutableint, float, str, bool, tuple. Operations return a new value.
  • Mutablelist, 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.