Skip to main content
Python beginner Lesson 5 of 28

Python Data Types

Master Python's built-in data types: int, float, str, bool, None, and how to inspect and convert between them.

The Core Built-in Types

Python has a small set of primitive types that everything else is built from. Getting them right early prevents bugs that are surprisingly hard to track down later — especially around float precision, boolean coercion, and the None vs falsy value distinction.

int

Python integers have arbitrary precision — there is no overflow, no matter how large the number gets. This is different from most other languages, where integers wrap around at a fixed bit size.

x = 42
big = 10 ** 100          # Googol — works fine, no overflow
negative = -17
hex_val = 0xFF           # 255 — hexadecimal literal
octal_val = 0o17         # 15  — octal literal
binary_val = 0b1010      # 10  — binary literal

# Underscores improve readability of large numbers (Python 3.6+)
million = 1_000_000
billion = 1_000_000_000

Integer division behavior is one of Python 3’s key changes from Python 2 — / now always returns a float:

7 // 2    # 3    — floor division: always rounds toward negative infinity
7 / 2     # 3.5  — true division: always returns a float
7 % 2     # 1    — modulo: the remainder after floor division

float

Floats are 64-bit IEEE 754 doubles — the same format used by virtually every other language. The precision pitfall they carry is not a Python bug; it’s a consequence of representing decimal fractions in binary.

pi = 3.14159
sci = 1.5e10      # 15000000000.0  — scientific notation
neg_exp = 2.5e-3  # 0.0025

# The classic precision pitfall — affects every language using IEEE 754
0.1 + 0.2 == 0.3   # False!
0.1 + 0.2           # 0.30000000000000004

# Fix with round() when displaying to users
round(0.1 + 0.2, 1) == 0.3   # True

# Fix with decimal for financial math — never use float for money
from decimal import Decimal
Decimal("0.1") + Decimal("0.2")  # Decimal('0.3') — exact

Special float values exist for edge cases like division limits and invalid math results:

import math
math.inf        # positive infinity — useful as an initial value for min-finding
-math.inf       # negative infinity — useful as an initial value for max-finding
math.nan        # Not a Number — result of invalid operations like 0.0 / 0.0
math.isnan(math.nan)   # True — always use isnan(), not ==
math.isinf(math.inf)   # True

str

Strings are immutable sequences of Unicode code points. Every string method returns a new string — the original is never modified. Python 3 made all strings Unicode by default, which means you can use any language or emoji without special handling.

name = "Alice"
multiline = """Line one
Line two"""
raw = r"C:\Users\alice\file.txt"   # raw string: backslashes are not escape chars

# f-strings (Python 3.6+) — the preferred way to format strings
age = 30
greeting = f"Hello, {name}! You are {age} years old."

# Any Python expression works inside {}
result = f"2 + 2 = {2 + 2}"
formatted = f"Pi is approximately {3.14159:.2f}"   # "Pi is approximately 3.14"

# Common string operations
"hello".upper()          # "HELLO"
"  spaces  ".strip()     # "spaces"
"a,b,c".split(",")       # ["a", "b", "c"]
"-".join(["a", "b"])     # "a-b"
"hello world".replace("world", "Python")  # "hello Python"

Strings are sequences, so the same indexing and slicing syntax used on lists works here too:

s = "Python"
s[0]      # "P"   — first character
s[-1]     # "n"   — last character
s[1:4]    # "yth" — characters at index 1, 2, 3
s[::-1]   # "nohtyP" — reversed (step of -1)

bool

bool is a subclass of int, which means True and False are literally 1 and 0. This has practical consequences — you can sum a list of booleans to count how many are True.

True + True    # 2 — bool arithmetic works because bool inherits from int
bool(0)        # False
bool(1)        # True
bool("")       # False — empty string is falsy
bool("hello")  # True  — any non-empty string is truthy
bool([])       # False — empty list is falsy
bool([0])      # True  — non-empty list, even with falsy elements, is truthy

Memorizing Python’s falsy values prevents bugs in conditional checks:

# All of these evaluate to False in a boolean context:
False, None, 0, 0.0, 0j, "", [], {}, set(), ()

None

None is Python’s null value — it represents “no value” or “not yet set.” It’s a singleton: there is exactly one None object in any Python process. Use it to signal optional values, uninitialized state, or missing results.

result = None

# Always check for None with 'is', not '=='
# 'is' checks identity (same object), which is correct for a singleton
if result is None:
    print("No result yet")

# This works but is misleading — it implies None might not be a singleton
if result == None:  # avoid
    ...

type() and isinstance()

Inspecting types at runtime is useful for debugging and for writing functions that handle multiple input types. isinstance() is almost always preferable to type() because it respects inheritance.

type(42)          # <class 'int'>
type("hello")     # <class 'str'>
type(3.14)        # <class 'float'>
type(True)        # <class 'bool'>
type(None)        # <class 'NoneType'>

# isinstance() is aware of inheritance — bool IS-A int
isinstance(42, int)           # True
isinstance(True, int)         # True  — because bool subclasses int
isinstance(42, (int, float))  # True  — check against a tuple of types at once

Type Conversion

Python doesn’t implicitly convert between types (except in a few arithmetic cases). Explicit conversion makes your intent clear and avoids silent data loss.

int("42")        # 42   — parses a decimal string
int(3.9)         # 3    — truncates toward zero, does NOT round
float("3.14")    # 3.14
str(100)         # "100"
bool(0)          # False
list("abc")      # ["a", "b", "c"] — iterates the string

# int() accepts a base argument for non-decimal strings
int("FF", 16)    # 255  — hexadecimal
int("1010", 2)   # 10   — binary

Type Hints

Type hints don’t affect runtime behavior — Python ignores them at execution time. Their value is for tooling: editors use them for autocomplete, mypy uses them to catch type errors before you run the code, and they serve as inline documentation for anyone reading the function signature.

def add(a: int, b: int) -> int:
    return a + b

def greet(name: str) -> str:
    return f"Hello, {name}"

# Optional signals that the return value might be None
from typing import Optional

def find_user(user_id: int) -> Optional[str]:
    if user_id == 1:
        return "Alice"
    return None  # explicit None is self-documenting

In Python 3.10+, you can use the union syntax directly without importing from typing:

def find_user(user_id: int) -> str | None:
    ...

Common Pitfalls

Mutable default arguments — one of Python’s most infamous gotchas. Default argument values are evaluated once when the function is defined, not each time it’s called. A mutable default (like a list or dict) is shared across every call.

# Wrong — the list is created once and shared across all calls
def append_to(item, lst=[]):
    lst.append(item)
    return lst

append_to(1)  # [1]
append_to(2)  # [1, 2]  — not [2]! The same list was reused.

# Correct — use None as the sentinel, create a fresh list each call
def append_to(item, lst=None):
    if lst is None:
        lst = []
    lst.append(item)
    return lst

Integer identity caching — CPython caches small integers (-5 to 256) as an optimization, which means is comparisons appear to work for small ints but break for larger ones. Never use is to compare integers.

a = 256
b = 256
a is b   # True  — CPython caches this range

a = 257
b = 257
a is b   # False — outside the cache, different objects
# Always use == for value comparison, never is

Frequently Asked Questions

Is Python dynamically typed?
Yes. Variables don't have fixed types — the type lives on the object, not the variable. You can add type hints for tooling without affecting runtime behavior.
What's the difference between == and is?
== checks value equality. is checks identity (same object in memory). Use == for value comparisons; reserve is for None checks.
Why does 0.1 + 0.2 != 0.3 in Python?
Floating-point numbers are stored in binary, and 0.1 cannot be represented exactly. Use the decimal module or round() when precision matters.