Python Data Types: int, float, str, bool, and More
Python is dynamically typed, meaning you don't declare variable types explicitly. The interpreter infers types at runtime, giving you flexibility but also responsibility. Understanding data types...
Key Insights
- Python’s dynamic typing offers flexibility, but understanding data types prevents bugs and improves performance—knowing when to use
intvsfloatorlistvstuplematters in production code. - Immutability is a critical concept: strings and tuples cannot be modified in place, while lists and dictionaries can, affecting both memory usage and program behavior.
- Type conversion and truthiness evaluation follow specific rules that can surprise developers—empty collections evaluate to
False, and float precision issues require careful handling in financial or scientific applications.
Introduction to Python’s Type System
Python is dynamically typed, meaning you don’t declare variable types explicitly. The interpreter infers types at runtime, giving you flexibility but also responsibility. Understanding data types isn’t academic—it directly impacts your code’s correctness, performance, and maintainability.
Every value in Python is an object with a type. Built-in types like int, str, and list handle most programming tasks, while custom types (classes) let you model domain-specific concepts. The type() function reveals what you’re working with:
age = 25
name = "Alice"
is_active = True
print(type(age)) # <class 'int'>
print(type(name)) # <class 'str'>
print(type(is_active)) # <class 'bool'>
This matters because operations valid for one type fail for another. You can multiply strings but not add them to integers without conversion. Knowing your types prevents runtime errors.
Numeric Types: int and float
Python provides two primary numeric types: int for whole numbers and float for decimals. Integers have unlimited precision—you can represent numbers as large as memory allows. Floats follow IEEE 754 double-precision standard with finite precision.
# Integer operations
x = 10
y = 3
print(x + y) # 13
print(x - y) # 7
print(x * y) # 30
print(x ** y) # 1000 (exponentiation)
# Division behavior
print(x / y) # 3.3333333333333335 (always returns float)
print(x // y) # 3 (floor division, returns int)
print(x % y) # 1 (modulo, remainder)
Type conversion between numeric types is straightforward but lossy from float to int:
# Converting between types
float_num = 3.7
int_num = 5
converted_to_float = float(int_num) # 5.0
converted_to_int = int(float_num) # 3 (truncates, doesn't round)
# Use round() for rounding
rounded = round(float_num) # 4
Float precision issues are notorious. Never compare floats directly for equality:
# Float precision problem
result = 0.1 + 0.2
print(result) # 0.30000000000000004
print(result == 0.3) # False
# Better approach
from math import isclose
print(isclose(result, 0.3)) # True
# Or use Decimal for financial calculations
from decimal import Decimal
precise = Decimal('0.1') + Decimal('0.2')
print(precise) # 0.3
Text Type: str
Strings are immutable sequences of Unicode characters. You create them with single, double, or triple quotes. Immutability means every string operation creates a new string—you never modify the original.
# String creation
single = 'Hello'
double = "World"
triple = """Multi
line
string"""
# Indexing and slicing (zero-based)
text = "Python"
print(text[0]) # P
print(text[-1]) # n (negative indexing from end)
print(text[0:3]) # Pyt (slice from index 0 to 3, exclusive)
print(text[::2]) # Pto (every second character)
String formatting has evolved. F-strings (Python 3.6+) are the modern standard:
name = "Alice"
age = 30
# F-strings (preferred)
message = f"{name} is {age} years old"
print(message) # Alice is 30 years old
# With expressions
print(f"{name.upper()} is {age + 5} years old") # ALICE is 35 years old
# Format method (older but still useful)
message = "{} is {} years old".format(name, age)
# Formatting numbers
price = 19.99
print(f"Price: ${price:.2f}") # Price: $19.99
Common string methods solve everyday problems:
text = " Hello, World! "
print(text.strip()) # "Hello, World!" (remove whitespace)
print(text.upper()) # " HELLO, WORLD! "
print(text.lower()) # " hello, world! "
print(text.replace("World", "Python")) # " Hello, Python! "
print(text.split(",")) # [' Hello', ' World! ']
# Checking content
email = "user@example.com"
print(email.startswith("user")) # True
print(email.endswith(".com")) # True
print("@" in email) # True
Boolean Type: bool
Python has two boolean values: True and False. Comparison and logical operations return booleans, but Python’s truthiness concept extends beyond explicit booleans.
# Comparison operators return booleans
age = 25
print(age > 18) # True
print(age == 25) # True
print(age != 30) # True
# Logical operators
is_adult = age >= 18
has_license = True
can_drive = is_adult and has_license # True
# Short-circuit evaluation
result = False and expensive_function() # expensive_function() never called
Truthiness determines how non-boolean values behave in conditional contexts:
# Falsy values (evaluate to False)
print(bool(0)) # False
print(bool(0.0)) # False
print(bool("")) # False (empty string)
print(bool([])) # False (empty list)
print(bool({})) # False (empty dict)
print(bool(None)) # False
# Truthy values (everything else)
print(bool(1)) # True
print(bool(-1)) # True
print(bool("text")) # True
print(bool([1, 2])) # True
# Practical usage
user_input = ""
if not user_input:
print("No input provided") # This executes
Collection Types Overview: list, tuple, dict, set
Python’s collection types organize multiple values. Choose based on mutability, ordering, and uniqueness requirements.
# List: mutable, ordered, allows duplicates
numbers = [1, 2, 3, 2]
numbers.append(4)
numbers[0] = 10
print(numbers) # [10, 2, 3, 2, 4]
# Tuple: immutable, ordered, allows duplicates
coordinates = (10, 20)
# coordinates[0] = 15 # TypeError: tuples are immutable
# Dictionary: mutable, ordered (Python 3.7+), unique keys
person = {"name": "Alice", "age": 30}
person["email"] = "alice@example.com"
print(person["name"]) # Alice
# Set: mutable, unordered, unique elements
unique_numbers = {1, 2, 3, 2}
print(unique_numbers) # {1, 2, 3}
unique_numbers.add(4)
| Type | Mutable | Ordered | Duplicates | Use Case |
|---|---|---|---|---|
| list | Yes | Yes | Yes | General sequences, stack/queue |
| tuple | No | Yes | Yes | Fixed collections, dict keys |
| dict | Yes | Yes (3.7+) | Keys: No | Key-value mappings |
| set | Yes | No | No | Membership testing, deduplication |
Type Checking and Conversion
Use isinstance() for type checking, not type(). It respects inheritance and handles multiple types:
# isinstance() is preferred
value = 42
print(isinstance(value, int)) # True
print(isinstance(value, (int, float))) # True (checks multiple types)
# type() for exact type matching
print(type(value) == int) # True
# Practical example
def process_data(data):
if isinstance(data, str):
return data.upper()
elif isinstance(data, (int, float)):
return data * 2
else:
raise TypeError(f"Unsupported type: {type(data)}")
Type conversion functions are explicit and predictable, but handle errors:
# Safe conversion with error handling
def safe_int_convert(value):
try:
return int(value)
except (ValueError, TypeError):
return None
print(safe_int_convert("123")) # 123
print(safe_int_convert("abc")) # None
print(safe_int_convert(3.7)) # 3
# Common conversion pitfalls
print(int("10.5")) # ValueError: invalid literal
print(int(float("10.5"))) # 10 (works)
# String to list
text = "hello"
print(list(text)) # ['h', 'e', 'l', 'l', 'o']
Best Practices and Common Pitfalls
Type hints improve code readability and enable static analysis tools. They don’t enforce types at runtime but document intent:
from typing import List, Dict, Optional
def greet(name: str, times: int = 1) -> str:
return (f"Hello, {name}! " * times).strip()
def process_items(items: List[int]) -> Dict[str, int]:
return {
"sum": sum(items),
"count": len(items)
}
# Optional for nullable values
def find_user(user_id: int) -> Optional[str]:
users = {1: "Alice", 2: "Bob"}
return users.get(user_id) # Returns None if not found
Never use mutable objects as default arguments. Python evaluates defaults once at function definition:
# WRONG: mutable default argument
def add_item_wrong(item, items=[]):
items.append(item)
return items
print(add_item_wrong(1)) # [1]
print(add_item_wrong(2)) # [1, 2] - unexpected!
# CORRECT: use None as default
def add_item_correct(item, items=None):
if items is None:
items = []
items.append(item)
return items
print(add_item_correct(1)) # [1]
print(add_item_correct(2)) # [2] - as expected
Choose the right type for your use case. Use tuples for fixed-size collections that shouldn’t change. Use sets for membership testing—item in set is O(1) versus O(n) for lists. Use dictionaries for lookups instead of searching through lists.
Understanding Python’s data types isn’t just about syntax—it’s about writing correct, efficient, and maintainable code. Master these fundamentals, and you’ll avoid entire categories of bugs while writing more Pythonic code.