Why Data Types Matter
Every piece of data your program works with has a type. When you write age = 25, Python doesn't just store the number — it stores metadata that says "this value is an integer." Understanding data types is fundamental for three reasons:
1. Type Safety and Correctness
Operations behave differently depending on the type of values involved. The + operator, for example, adds two numbers but concatenates two strings. If you accidentally mix types without converting, Python raises a TypeError:
# This works — both operands are the same type
print(10 + 5) # 15
print("Hello" + " World") # Hello World
# This fails — you can't add a string to an integer
# print("Age: " + 25) # TypeError: can only concatenate str to str
print("Age: " + str(25)) # Age: 25 — explicit conversion fixes it
2. Memory and Performance
Different types consume different amounts of memory. An integer 1 and a string "1" are stored very differently internally. When you process millions of records, choosing the right type (e.g., using integers instead of strings for numeric IDs) can make your program significantly faster.
3. Debugging
A huge percentage of beginner bugs boil down to type mismatches — comparing a string "25" to an integer 25, or trying to do arithmetic on the result of input() (which always returns a string). Knowing your types is the fastest path to diagnosing these errors.
Core Data Types at a Glance
Python has several built-in data types. This chapter focuses on the scalar (single-value) types. Collection types like lists, tuples, dictionaries, and sets are covered in dedicated chapters.
| Category | Type | Description | Example |
|---|---|---|---|
| Numeric | int | Whole numbers, unlimited precision | 42, -7, 0, 1_000_000 |
| Numeric | float | Decimal (floating-point) numbers | 3.14, -0.5, 1.0e6 |
| Numeric | complex | Complex numbers with real and imaginary parts | 3+4j, complex(3, 4) |
| Text | str | Immutable sequences of characters | "hello", 'world', """block""" |
| Boolean | bool | Logical truth values | True, False |
| None | NoneType | Represents the absence of a value | None |
Numeric Types in Detail
Integers (int)
Python integers have unlimited precision — they can be as large or as small as your machine's memory allows. There is no overflow like in C or Java where int is limited to 32 or 64 bits.
# Regular integers
x = 42
y = -17
z = 0
# Very large integers — Python handles them natively
big = 99999999999999999999999999999999
print(big + 1) # 100000000000000000000000000000000
# Use underscores for readability (Python 3.6+)
population = 1_400_000_000
budget = 2_50_000 # Indian numbering style works too
print(population) # 1400000000
Integer Bases
You can write integers in binary, octal, or hexadecimal using prefixes:
# Binary (base 2) — prefix 0b or 0B
binary_val = 0b1010
print(binary_val) # 10
# Octal (base 8) — prefix 0o or 0O
octal_val = 0o17
print(octal_val) # 15
# Hexadecimal (base 16) — prefix 0x or 0X
hex_val = 0xFF
print(hex_val) # 255
# Converting integers TO different base strings
print(bin(255)) # 0b11111111
print(oct(255)) # 0o377
print(hex(255)) # 0xff
# Converting base strings BACK to integers
print(int("1010", 2)) # 10 — binary to int
print(int("17", 8)) # 15 — octal to int
print(int("FF", 16)) # 255 — hex to int
Useful Built-in Functions for Integers
# abs() — absolute value
print(abs(-42)) # 42
print(abs(42)) # 42
# pow() — exponentiation (same as **)
print(pow(2, 10)) # 1024
print(2 ** 10) # 1024
# pow() with three arguments — modular exponentiation (efficient)
# pow(base, exp, mod) computes (base ** exp) % mod
print(pow(2, 10, 1000)) # 24 (i.e., 1024 % 1000)
# divmod() — returns (quotient, remainder) as a tuple
print(divmod(17, 5)) # (3, 2) — because 17 = 5*3 + 2
print(divmod(100, 7)) # (14, 2)
# Practical use of divmod: converting seconds to minutes and seconds
total_seconds = 3725
minutes, seconds = divmod(total_seconds, 60)
hours, minutes = divmod(minutes, 60)
print(f"{hours}h {minutes}m {seconds}s") # 1h 2m 5s
Floats (float)
Floats represent decimal numbers. Internally, Python uses the IEEE 754 double-precision format (64-bit), which gives you roughly 15-17 significant decimal digits of precision.
# Creating floats
pi = 3.14159
temperature = -40.0
tiny = 0.000001
# Scientific notation (e or E)
speed_of_light = 3.0e8 # 300,000,000.0
planck = 6.626e-34 # 0.0000000000000000000000000000000006626
# A whole number becomes a float if you add a decimal point
x = 10 # int
y = 10.0 # float
print(type(x)) # <class 'int'>
print(type(y)) # <class 'float'>
Floating-Point Precision Issues
This is one of the most important things to understand in any programming language. Floats are stored in binary, and many decimal fractions (like 0.1) have no exact binary representation. This leads to tiny rounding errors:
# The classic surprise
print(0.1 + 0.2) # 0.30000000000000004
print(0.1 + 0.2 == 0.3) # False!
# More precision issues
print(0.1 + 0.1 + 0.1 - 0.3) # 5.551115123125783e-17 (not exactly 0)
How to handle this:
import math
# Option 1: math.isclose() — recommended for comparisons
print(math.isclose(0.1 + 0.2, 0.3)) # True
# You can control the tolerance
print(math.isclose(0.1 + 0.2, 0.3, rel_tol=1e-9)) # True
# Option 2: round() for display purposes
result = 0.1 + 0.2
print(round(result, 1)) # 0.3
# Option 3: decimal module for exact decimal arithmetic
from decimal import Decimal
print(Decimal("0.1") + Decimal("0.2")) # 0.3 (exact)
# IMPORTANT: Use strings with Decimal, not floats
# Decimal(0.1) still has the imprecision — always use Decimal("0.1")
Rule of thumb: Never compare floats with
==. Usemath.isclose()or round to a fixed number of decimal places. For financial calculations, use thedecimalmodule.
Special Float Values
Python supports three special float values:
# Positive infinity
pos_inf = float('inf')
print(pos_inf > 999999999999) # True
print(pos_inf + 1) # inf
print(pos_inf + pos_inf) # inf
# Negative infinity
neg_inf = float('-inf')
print(neg_inf < -999999999999) # True
# Not a Number (NaN) — represents undefined results
nan = float('nan')
print(nan == nan) # False! NaN is not equal to itself
print(nan != nan) # True
# Use math.isnan() and math.isinf() to check
import math
print(math.isnan(nan)) # True
print(math.isinf(pos_inf)) # True
print(math.isfinite(3.14)) # True
print(math.isfinite(pos_inf)) # False
Complex Numbers (complex)
Python has built-in support for complex numbers. You'll encounter these mainly in scientific computing, signal processing, or electrical engineering. For everyday programming, you can safely skip this section.
# Creating complex numbers — use j (not i) for the imaginary part
z = 3 + 4j
print(z) # (3+4j)
print(type(z)) # <class 'complex'>
# Accessing parts
print(z.real) # 3.0
print(z.imag) # 4.0
# Using the constructor
z2 = complex(3, 4) # same as 3 + 4j
# Arithmetic
print(z + z2) # (6+8j)
print(abs(z)) # 5.0 — magnitude (sqrt(3^2 + 4^2))
Common Numeric Functions
These functions work across numeric types:
# round() — round to n decimal places
print(round(3.14159)) # 3 (rounds to nearest int)
print(round(3.14159, 2)) # 3.14
print(round(3.145, 2)) # 3.14 (banker's rounding — rounds to even)
print(round(3.155, 2)) # 3.15
print(round(2.5)) # 2 (banker's rounding — not 3!)
print(round(3.5)) # 4
# abs() — absolute value
print(abs(-7)) # 7
print(abs(-3.14)) # 3.14
# min() and max()
print(min(3, 1, 4, 1, 5)) # 1
print(max(3, 1, 4, 1, 5)) # 5
# Also work with iterables
numbers = [23, 45, 12, 67, 34]
print(min(numbers)) # 12
print(max(numbers)) # 67
# sum() — add all elements
print(sum([1, 2, 3, 4, 5])) # 15
print(sum([1, 2, 3], 10)) # 16 (start value = 10)
print(sum(range(1, 101))) # 5050 (sum of 1 to 100)
| Function | Description | Example | Result |
|---|---|---|---|
abs(x) | Absolute value | abs(-5) | 5 |
round(x, n) | Round to n decimal places | round(3.14159, 2) | 3.14 |
pow(x, y) | x raised to power y | pow(2, 10) | 1024 |
divmod(x, y) | Quotient and remainder | divmod(17, 5) | (3, 2) |
min(...) | Smallest value | min(3, 1, 4) | 1 |
max(...) | Largest value | max(3, 1, 4) | 4 |
sum(iterable) | Sum of all elements | sum([1, 2, 3]) | 6 |
Strings in Detail
Strings are sequences of Unicode characters. This section gives a thorough preview — strings have their own dedicated chapter later with even more depth on methods, regular expressions, and encoding.
Creating Strings
# Single quotes
name = 'Meritshot'
# Double quotes — functionally identical
name = "Meritshot"
# Use single quotes inside double, or vice versa
message = "It's a beautiful day"
html = '<div class="container">Hello</div>'
# Triple quotes — for multi-line strings
bio = """This is a
multi-line string.
It preserves line breaks."""
# Triple single quotes work too
poem = '''Roses are red,
Violets are blue,
Python is awesome,
And so are you.'''
print(bio)
# Output:
# This is a
# multi-line string.
# It preserves line breaks.
Escape Characters
When you need special characters inside a string, use the backslash \:
# Common escape characters
print("Hello\nWorld") # newline — prints on two lines
print("Name:\tPriya") # tab
print("She said \"Hi\"") # escaped double quote
print('It\'s fine') # escaped single quote
print("C:\\Users\\docs") # escaped backslash
# Unicode characters
print("\u2764") # prints a heart symbol
print("\u20B9") # ₹ (Indian Rupee sign)
| Escape | Description | Example | Output |
|---|---|---|---|
\n | Newline | "Line1\nLine2" | Line1 (newline) Line2 |
\t | Tab | "Col1\tCol2" | Col1 (tab) Col2 |
\\ | Literal backslash | "C:\\path" | C:\path |
\" | Double quote | "say \"hi\"" | say "hi" |
\' | Single quote | 'it\'s' | it's |
\0 | Null character | "end\0here" | end (null) here |
Raw Strings
Prefix a string with r to disable escape character processing. This is very useful for Windows file paths and regular expressions:
# Without raw string — \n is interpreted as newline
path = "C:\new_folder\test"
print(path)
# C:
# ew_folder est (oops!)
# With raw string — backslashes are literal
path = r"C:\new_folder\test"
print(path) # C:\new_folder\test
# Useful for regex patterns
import re
pattern = r"\d{3}-\d{4}" # matches 123-4567
Immutability
Strings in Python are immutable — once created, you cannot change individual characters. Any operation that "modifies" a string actually creates a new one:
name = "Python"
# This will NOT work
# name[0] = "J" # TypeError: 'str' object does not support item assignment
# Instead, create a new string
new_name = "J" + name[1:]
print(new_name) # Jython
# String methods also return NEW strings
greeting = "hello"
upper_greeting = greeting.upper()
print(greeting) # hello — original unchanged
print(upper_greeting) # HELLO — new string
Indexing and Slicing
Strings are sequences, so you can access individual characters by position (index) and extract substrings using slicing:
text = "Meritshot"
# M e r i t s h o t
# Index 0 1 2 3 4 5 6 7 8
# Neg -9 -8 -7 -6 -5 -4 -3 -2 -1
# Indexing — access a single character
print(text[0]) # M (first character)
print(text[5]) # s
print(text[-1]) # t (last character)
print(text[-3]) # h (third from end)
# Slicing — extract a substring [start:stop:step]
# start is inclusive, stop is exclusive
print(text[0:5]) # Merit
print(text[5:9]) # shot
print(text[:5]) # Merit (start defaults to 0)
print(text[5:]) # shot (stop defaults to end)
print(text[:]) # Meritshot (full copy)
# Step (every nth character)
print(text[::2]) # Mrsto (every 2nd character)
print(text[::-1]) # tohstireM (reversed!)
# Practical: extract file extension
filename = "report_2026.pdf"
extension = filename[filename.rfind("."):] # .pdf
Common String Methods
Python strings come with dozens of built-in methods. Here are the most important ones:
text = " Hello, World! "
# --- Case Methods ---
print(text.upper()) # " HELLO, WORLD! "
print(text.lower()) # " hello, world! "
print(text.title()) # " Hello, World! "
print(text.capitalize()) # " hello, world! " (only first char)
print(text.swapcase()) # " hELLO, wORLD! "
# --- Whitespace Methods ---
print(text.strip()) # "Hello, World!" (removes leading/trailing whitespace)
print(text.lstrip()) # "Hello, World! " (left only)
print(text.rstrip()) # " Hello, World!" (right only)
# --- Search Methods ---
sentence = "Python is powerful and Python is popular"
print(sentence.find("Python")) # 0 (first occurrence index)
print(sentence.find("Python", 5)) # 25 (start searching from index 5)
print(sentence.find("Java")) # -1 (not found)
print(sentence.count("Python")) # 2
print(sentence.startswith("Python")) # True
print(sentence.endswith("popular")) # True
# --- Replace and Split ---
print(sentence.replace("Python", "Java"))
# "Java is powerful and Java is popular"
print(sentence.replace("Python", "Java", 1))
# "Java is powerful and Python is popular" (replace only first)
words = "apple,banana,cherry"
print(words.split(",")) # ['apple', 'banana', 'cherry']
print(words.split(",", 1)) # ['apple', 'banana,cherry'] (split once)
# Default split() splits on any whitespace and removes empty strings
messy = " Hello World Python "
print(messy.split()) # ['Hello', 'World', 'Python']
# --- Join (the reverse of split) ---
fruits = ['apple', 'banana', 'cherry']
print(", ".join(fruits)) # "apple, banana, cherry"
print(" -> ".join(fruits)) # "apple -> banana -> cherry"
print("\n".join(fruits)) # each on a new line
# --- Checking Content ---
print("12345".isdigit()) # True
print("hello".isalpha()) # True
print("hello123".isalnum()) # True
print(" ".isspace()) # True
| Method | Description | Example | Result |
|---|---|---|---|
upper() | All uppercase | "hi".upper() | "HI" |
lower() | All lowercase | "HI".lower() | "hi" |
strip() | Remove whitespace | " hi ".strip() | "hi" |
replace(a, b) | Replace substring | "hi".replace("h","b") | "bi" |
split(sep) | Split into list | "a,b".split(",") | ["a","b"] |
join(list) | Join list into string | ",".join(["a","b"]) | "a,b" |
find(sub) | Index of substring | "hello".find("ll") | 2 |
count(sub) | Count occurrences | "hello".count("l") | 2 |
startswith(s) | Starts with prefix | "hello".startswith("he") | True |
endswith(s) | Ends with suffix | "hello".endswith("lo") | True |
f-Strings (Formatted String Literals)
f-strings (Python 3.6+) are the modern, preferred way to embed expressions inside strings. Prefix the string with f and put expressions inside {}:
name = "Priya"
score = 95.678
items = 1_250_000
# Basic variable insertion
print(f"Hello, {name}!") # Hello, Priya!
# Expressions inside braces
print(f"2 + 3 = {2 + 3}") # 2 + 3 = 5
print(f"Name length: {len(name)}") # Name length: 5
# --- Number Formatting ---
# Fixed decimal places
print(f"Score: {score:.2f}") # Score: 95.68
# Thousands separator with comma
print(f"Items: {items:,}") # Items: 1,250,000
# Combining comma separator and decimal places
salary = 1250000.5
print(f"Salary: ₹{salary:,.2f}") # Salary: ₹1,250,000.50
# Percentage
ratio = 0.856
print(f"Accuracy: {ratio:.1%}") # Accuracy: 85.6%
# Padding with zeros
order_id = 42
print(f"Order #{order_id:05d}") # Order #00042
# --- Alignment and Padding ---
# Left-aligned (default for strings)
print(f"{'Name':<20}{'Score':>10}")
print(f"{'Priya':<20}{95:>10}")
print(f"{'Rahul':<20}{87:>10}")
# Output:
# Name Score
# Priya 95
# Rahul 87
# Center-aligned
print(f"{'REPORT':=^40}")
# =================REPORT=================
# --- Advanced: Expressions and Method Calls ---
data = [85, 92, 78, 95, 88]
print(f"Average: {sum(data)/len(data):.1f}") # Average: 87.6
print(f"Name: {name.upper()!r}") # Name: 'PRIYA'
| Format Spec | Description | Example | Result |
|---|---|---|---|
:.2f | 2 decimal places | f"{3.14159:.2f}" | 3.14 |
:, | Thousands separator | f"{1000000:,}" | 1,000,000 |
:.1% | Percentage (1 dp) | f"{0.85:.1%}" | 85.0% |
:05d | Zero-padded integer | f"{42:05d}" | 00042 |
:<20 | Left-align, width 20 | f"{'hi':<20}" | hi |
:>20 | Right-align, width 20 | f"{'hi':>20}" | hi |
:^20 | Center, width 20 | f"{'hi':^20}" | hi |
String Multiplication and Concatenation
# Concatenation with +
first = "Merit"
last = "shot"
full = first + last
print(full) # Meritshot
# Repetition with *
line = "-" * 40
print(line) # ----------------------------------------
# Practical: creating a simple banner
title = "Python Data Types"
border = "=" * (len(title) + 4)
print(border)
print(f"| {title} |")
print(border)
# =====================
# | Python Data Types |
# =====================
# Concatenation in a loop (INEFFICIENT for many items)
# Use join() instead:
words = ["Python", "is", "awesome"]
sentence = " ".join(words) # "Python is awesome" — much faster
Booleans
Booleans represent one of two values: True or False. They are the foundation of all decision-making in your programs.
Basics
is_active = True
is_deleted = False
print(type(is_active)) # <class 'bool'>
# Booleans from comparisons
print(10 > 5) # True
print(3 == 7) # False
print("a" != "b") # True
print(5 >= 5) # True
Booleans Are Integers
In Python, bool is a subclass of int. True is literally 1 and False is literally 0:
print(True == 1) # True
print(False == 0) # True
print(True + True) # 2
print(True * 10) # 10
print(False + 5) # 5
# This means you can use booleans in arithmetic
# Practical: counting True values in a list
results = [True, False, True, True, False]
print(sum(results)) # 3 (counts how many are True)
# isinstance confirms bool is a subclass of int
print(isinstance(True, int)) # True
print(isinstance(True, bool)) # True
Truthy and Falsy Values
In Python, every value has a boolean interpretation. When used in a boolean context (like an if statement), values are either "truthy" (treated as True) or "falsy" (treated as False).
Falsy values (everything else is truthy):
| Type | Falsy Value |
|---|---|
bool | False |
NoneType | None |
int | 0 |
float | 0.0 |
complex | 0j |
str | "" (empty string) |
list | [] (empty list) |
tuple | () (empty tuple) |
dict | {} (empty dict) |
set | set() (empty set) |
range | range(0) (empty range) |
# Demonstrating truthy/falsy behaviour
if "hello":
print("Non-empty string is truthy") # This prints
if "":
print("This won't print") # Skipped
if 42:
print("Non-zero number is truthy") # This prints
if 0:
print("This won't print") # Skipped
if [1, 2, 3]:
print("Non-empty list is truthy") # This prints
if []:
print("This won't print") # Skipped
# Practical: check if a list has items
shopping_list = ["milk", "eggs"]
if shopping_list:
print(f"You have {len(shopping_list)} items to buy")
else:
print("Your shopping list is empty")
The bool() Function
You can explicitly convert any value to a boolean using bool():
# Numbers
print(bool(0)) # False
print(bool(0.0)) # False
print(bool(42)) # True
print(bool(-1)) # True (any non-zero number)
# Strings
print(bool("")) # False
print(bool("hello")) # True
print(bool(" ")) # True (a space is NOT empty)
print(bool("False")) # True (the STRING "False" is non-empty!)
# Collections
print(bool([])) # False
print(bool([0])) # True (list is not empty, even though element is 0)
# None
print(bool(None)) # False
Common gotcha:
bool("False")isTruebecause the string"False"is non-empty. To convert the string"False"to the booleanFalse, you need custom logic.
NoneType
What is None?
None is Python's way of representing "nothing" or "no value." It is the sole instance of the NoneType class and is a singleton — there is only ever one None object in memory.
result = None
print(result) # None
print(type(result)) # <class 'NoneType'>
When You'll Encounter None
# 1. Default function return value
def greet(name):
print(f"Hello, {name}!")
# No explicit return statement
result = greet("Priya")
print(result) # None — functions return None by default
# 2. Initialising a variable that will be assigned later
user_input = None
best_score = None
# 3. Optional function parameters
def find_user(name, age=None):
if age is None:
print(f"Searching for {name} (any age)")
else:
print(f"Searching for {name}, age {age}")
find_user("Priya") # Searching for Priya (any age)
find_user("Priya", 25) # Searching for Priya, age 25
# 4. Indicating missing data
data = {"name": "Priya", "email": None}
# email is explicitly set to None — meaning "no email provided"
is None vs == None
Always use is None (or is not None) rather than == None. The is operator checks identity (same object in memory), which is both faster and more correct:
x = None
# Preferred — identity check
if x is None:
print("x is None")
if x is not None:
print("x has a value")
# Avoid — equality check (can be overridden by custom classes)
if x == None: # works, but not recommended
print("x is None")
The reason is is preferred: a custom class could override the __eq__ method and make some_object == None return True even when the object is not actually None. The is operator cannot be overridden and always checks true identity.
Type Checking
The type() Function
type() returns the exact type of a value:
print(type(42)) # <class 'int'>
print(type(3.14)) # <class 'float'>
print(type("hello")) # <class 'str'>
print(type(True)) # <class 'bool'>
print(type(None)) # <class 'NoneType'>
print(type([1, 2])) # <class 'list'>
# You can compare types directly
x = 42
if type(x) == int:
print("x is an integer")
The isinstance() Function (Preferred)
isinstance() checks if a value is an instance of a given type or any of its subclasses. This makes it more flexible and Pythonic:
x = 42
# Basic usage
print(isinstance(x, int)) # True
print(isinstance(x, float)) # False
print(isinstance(x, str)) # False
# Check against multiple types at once (tuple of types)
print(isinstance(x, (int, float))) # True — x is an int OR float
# Works with inheritance
print(isinstance(True, int)) # True — bool is a subclass of int
print(type(True) == int) # False — type() checks exact type only
Why isinstance() is Better Than type()
# Example: bool is a subclass of int
value = True
# type() — too strict, misses the relationship
if type(value) == int:
print("It's an int") # Does NOT print
if type(value) == bool:
print("It's a bool") # Prints
# isinstance() — respects inheritance
if isinstance(value, int):
print("It's an int") # Prints (because bool IS an int)
if isinstance(value, bool):
print("It's a bool") # Also prints
# Practical: accepting both int and float
def calculate_tax(amount):
if not isinstance(amount, (int, float)):
raise TypeError("amount must be a number")
return amount * 0.18
print(calculate_tax(1000)) # 180.0
print(calculate_tax(99.99)) # 17.9982
Type Conversion (Casting)
Type conversion is the process of converting a value from one type to another. Python supports both implicit (automatic) and explicit (manual) conversion.
Implicit Conversion
Python automatically converts types when it can do so without losing data:
# int + float = float (int is "promoted" to float)
result = 10 + 3.14
print(result) # 13.14
print(type(result)) # <class 'float'>
# int + bool = int (True becomes 1, False becomes 0)
result = 10 + True
print(result) # 11
# Python does NOT implicitly convert between str and numbers
# print("Age: " + 25) # TypeError!
Explicit Conversion
Use built-in functions to manually convert between types:
# --- int() ---
print(int(3.99)) # 3 (truncates toward zero, does NOT round)
print(int(-3.99)) # -3
print(int("42")) # 42
print(int("0b1010", 2)) # 10 (binary string)
print(int(True)) # 1
print(int(False)) # 0
# --- float() ---
print(float(42)) # 42.0
print(float("3.14")) # 3.14
print(float("inf")) # inf
print(float(True)) # 1.0
# --- str() ---
print(str(42)) # "42"
print(str(3.14)) # "3.14"
print(str(True)) # "True"
print(str(None)) # "None"
print(str([1, 2, 3])) # "[1, 2, 3]"
# --- bool() ---
print(bool(0)) # False
print(bool(42)) # True
print(bool("")) # False
print(bool("hello")) # True
print(bool([])) # False
print(bool(None)) # False
# --- Collection conversions (preview) ---
print(list("hello")) # ['h', 'e', 'l', 'l', 'o']
print(tuple([1, 2, 3])) # (1, 2, 3)
print(set([1, 2, 2, 3])) # {1, 2, 3} (duplicates removed)
print(list(range(5))) # [0, 1, 2, 3, 4]
Common Conversion Pitfalls
# PITFALL 1: int() cannot parse a decimal string directly
# print(int("3.14")) # ValueError: invalid literal for int()
# Fix: convert to float first, then to int
print(int(float("3.14"))) # 3
# PITFALL 2: int() truncates, it doesn't round
print(int(9.9)) # 9 (not 10)
print(round(9.9)) # 10 (use round() if you want rounding)
# PITFALL 3: converting non-numeric strings to numbers
# print(int("hello")) # ValueError
# print(float("abc")) # ValueError
# Safe conversion using try/except
def safe_int(value):
"""Convert to int safely, return None on failure."""
try:
return int(value)
except (ValueError, TypeError):
return None
print(safe_int("42")) # 42
print(safe_int("hello")) # None
print(safe_int(None)) # None
# PITFALL 4: str(list) gives a string representation, not elements
my_list = [1, 2, 3]
print(str(my_list)) # "[1, 2, 3]" — a string, not usable as list
# To join list elements into a string:
print(", ".join(str(x) for x in my_list)) # "1, 2, 3"
Conversion Reference Table
| From | To int | To float | To str | To bool |
|---|---|---|---|---|
int 42 | 42 | 42.0 | "42" | True |
int 0 | 0 | 0.0 | "0" | False |
float 3.14 | 3 (truncated) | 3.14 | "3.14" | True |
float 0.0 | 0 | 0.0 | "0.0" | False |
str "42" | 42 | 42.0 | "42" | True |
str "" | ValueError | ValueError | "" | False |
bool True | 1 | 1.0 | "True" | True |
bool False | 0 | 0.0 | "False" | False |
None | TypeError | TypeError | "None" | False |
Dynamic Typing Explained
Variables Are Labels, Not Boxes
In many languages (like C or Java), a variable is a named memory location (a "box") that holds a value of a fixed type. In Python, variables work differently. A variable is a label (or reference) that points to an object in memory. The type belongs to the object, not the variable.
x = 42 # x points to an int object 42
print(type(x)) # <class 'int'>
x = "hello" # now x points to a str object "hello"
print(type(x)) # <class 'str'>
x = [1, 2, 3] # now x points to a list object
print(type(x)) # <class 'list'>
# The variable x has no fixed type —
# it's just a name that can refer to any object
The id() Function
Every object in Python has a unique identifier (its memory address). The id() function reveals it:
a = 42
b = 42
print(id(a)) # e.g., 140712345678
print(id(b)) # same as id(a)!
print(a is b) # True — they point to the same object
# Python caches small integers (-5 to 256) and short strings
# for efficiency (this is called "interning")
c = 1000
d = 1000
print(c is d) # Might be True or False depending on context
# Reassignment creates a new object (for immutable types)
x = 10
print(id(x)) # e.g., 140712345000
x = 20
print(id(x)) # different — x now points to a new object
Mutable vs Immutable Types (Preview)
This concept is covered in depth in the collections chapters, but here is the key distinction:
Immutable types — cannot be changed after creation. Any "modification" creates a new object.
int,float,complex,str,tuple,bool,frozenset,None
Mutable types — can be changed in place. The same object is modified.
list,dict,set,bytearray
# Immutable — strings
name = "Priya"
# name[0] = "K" # TypeError — can't change a string in place
# Mutable — lists
scores = [90, 85, 92]
scores[0] = 95 # This works — lists are mutable
print(scores) # [95, 85, 92]
# Why this matters: aliasing
a = [1, 2, 3]
b = a # b points to the SAME list object
b.append(4)
print(a) # [1, 2, 3, 4] — a is also changed!
# For immutable types, this doesn't happen
a = "hello"
b = a
b = b.upper() # creates a new string
print(a) # "hello" — unchanged
print(b) # "HELLO"
Practical Examples
Example 1: Number Formatting for Currency
def format_currency(amount, currency="INR"):
"""Format a number as currency with proper symbols and grouping."""
symbols = {
"INR": "₹",
"USD": "$",
"EUR": "€",
"GBP": "£"
}
symbol = symbols.get(currency, currency + " ")
if amount < 0:
return f"-{symbol}{abs(amount):,.2f}"
return f"{symbol}{amount:,.2f}"
# Usage
print(format_currency(1250000)) # ₹1,250,000.00
print(format_currency(99.99, "USD")) # $99.99
print(format_currency(-500, "EUR")) # -€500.00
print(format_currency(1499.9, "GBP")) # £1,499.90
Example 2: Parsing User Input with Error Handling
def get_number(prompt, number_type=float):
"""
Safely get a number from the user.
Keeps asking until a valid number is entered.
"""
while True:
user_input = input(prompt)
# Allow the user to quit
if user_input.lower() in ("q", "quit", "exit"):
return None
try:
return number_type(user_input)
except ValueError:
print(f" Invalid input: '{user_input}' is not a valid number.")
print(f" Please enter a valid {number_type.__name__}.")
# Usage
# age = get_number("Enter your age: ", int)
# if age is not None:
# print(f"You are {age} years old")
# else:
# print("Goodbye!")
# Example of how the interaction would look:
# Enter your age: abc
# Invalid input: 'abc' is not a valid number.
# Please enter a valid int.
# Enter your age: 25.5
# Invalid input: '25.5' is not a valid number.
# Please enter a valid int.
# Enter your age: 25
# You are 25 years old
Example 3: Data Validation Function
def validate_user_data(name, age, email, score):
"""
Validate user data and return a dictionary of errors.
Returns an empty dict if all validations pass.
"""
errors = {}
# Validate name — must be a non-empty string
if not isinstance(name, str) or not name.strip():
errors["name"] = "Name must be a non-empty string"
# Validate age — must be a positive integer
if not isinstance(age, int) or isinstance(age, bool):
errors["age"] = "Age must be an integer"
elif age < 0 or age > 150:
errors["age"] = "Age must be between 0 and 150"
# Validate email — basic check (must contain @ and .)
if not isinstance(email, str):
errors["email"] = "Email must be a string"
elif "@" not in email or "." not in email.split("@")[-1]:
errors["email"] = "Email must be a valid format (user@domain.com)"
# Validate score — must be a number between 0 and 100
if not isinstance(score, (int, float)) or isinstance(score, bool):
errors["score"] = "Score must be a number"
elif score < 0 or score > 100:
errors["score"] = "Score must be between 0 and 100"
return errors
# Test cases
print(validate_user_data("Priya", 22, "priya@example.com", 95.5))
# {} — all valid
print(validate_user_data("", -5, "invalid", 150))
# {'name': 'Name must be a non-empty string',
# 'age': 'Age must be between 0 and 150',
# 'email': 'Email must be a valid format (user@domain.com)',
# 'score': 'Score must be between 0 and 100'}
print(validate_user_data("Rahul", 25, "rahul@mail.com", 87))
# {} — all valid
# Demonstrating the isinstance + bool edge case:
# isinstance(True, int) is True, so we explicitly exclude booleans
print(validate_user_data("Test", True, "a@b.c", False))
# {'age': 'Age must be an integer', 'score': 'Score must be a number'}
Practice Exercises
Test your understanding of Python data types with these exercises. Try to solve them yourself before looking at hints.
Exercise 1: Type Inspector
Write a function inspect_value(value) that takes any value and prints its type, its truthiness, and its string representation. Example output for inspect_value(42):
Value: 42
Type: <class 'int'>
Truthy: True
str(): '42'
Exercise 2: Floating-Point Detective
Write code that demonstrates the floating-point precision issue. Create a list of expressions where the mathematical result differs from Python's actual result. Use math.isclose() to verify the correct comparisons. Try at least three different examples beyond 0.1 + 0.2.
Exercise 3: Base Converter
Write a function convert_base(number, base) that takes an integer and a target base (2, 8, or 16) and returns the string representation. If an invalid base is provided, return an error message. Include the appropriate prefix (0b, 0o, 0x).
Exercise 4: Smart Type Caster
Write a function smart_cast(value) that takes a string and automatically converts it to the most appropriate type:
"42"should returnint42"3.14"should returnfloat3.14"True"or"False"should returnbool"None"should returnNone- Anything else stays as a string
Exercise 5: Receipt Formatter
Write a function that takes a list of tuples (item_name, quantity, unit_price) and prints a formatted receipt using f-strings. Include proper column alignment, subtotals per item, and a grand total. Example input:
items = [
("Notebook", 3, 45.00),
("Pen", 10, 12.50),
("Eraser", 5, 8.00),
("Ruler", 2, 35.00),
]
Expected output should have columns for item, quantity, price, and subtotal, all neatly aligned.
Exercise 6: Type Validation Decorator
(Advanced) Write a function validate_types(value, expected_types) that:
- Returns
Trueif the value matches any of the expected types - Handles the
bool/intsubclass quirk correctly (i.e.,Trueshould NOT pass as a validintunlessboolis also in the expected types) - Works with
Noneas an expected type
Summary
In this chapter, you took a deep dive into Python's data types:
- Numeric types —
int(unlimited precision, multiple bases),float(IEEE 754 with precision caveats), andcomplex(real + imaginary) - Strings — creation with different quote styles, escape characters, raw strings, immutability, indexing, slicing, common methods, and f-string formatting
- Booleans —
True/False, their relationship to integers, and the concept of truthy/falsy values - NoneType — what
Nonerepresents, when to use it, and whyis Noneis preferred over== None - Type checking —
type()vsisinstance()and why the latter is more Pythonic - Type conversion — implicit vs explicit casting, conversion functions, and common pitfalls like
int("3.14")failing - Dynamic typing — variables as labels, the
id()function, and the mutable vs immutable distinction
Key takeaways to remember:
- Never compare floats with
==— usemath.isclose() - Always use
is Noneinstead of== None - Prefer
isinstance()overtype()for type checking - Be aware of truthy/falsy values — empty collections and zero are falsy
input()always returns a string — convert explicitly when you need numbers
Next up: Conditional Statements — learn how to make decisions in your programs with if, elif, else, and write logic that responds to different situations.