Module 2.1

Data Types Deep Dive

Every piece of data in Python has a type. Understanding data types is crucial for writing correct, efficient code. Let's explore Python's built-in types in depth!

40 min read
Beginner
50+ Examples
What You'll Learn
  • Numeric types (int, float, complex)
  • Text type (str) and formatting
  • Boolean type and truthiness
  • None and its use cases
  • Type checking and conversion
Contents
01

What Are Data Types?

In the real world, we deal with different kinds of information: numbers, text, yes/no answers, and more. Python needs to know what kind of data you're working with so it can handle it correctly.

Real-World Analogy

Think of data types like different containers in your kitchen:

  • Measuring cup → Numbers (you can do math)
  • Label maker → Text (you can read and edit)
  • Light switch → Boolean (on/off, yes/no)
  • Empty container → None (nothing inside)
Why Does It Matter?

Different types have different behaviors. You can add two numbers (5 + 3 = 8), but "adding" two strings joins them ("Hello" + "World" = "HelloWorld"). Python needs to know the type to do the right thing!

Key Concept

Data Type

A data type is a classification that tells Python what kind of value a variable holds and what operations can be performed on it. Think of it like labeling boxes when you move house - you write "KITCHEN" on boxes with dishes so you know what's inside and where it belongs. Similarly, Python labels each piece of data with a type so it knows how to handle it.

Python is dynamically typed, which means you don't need to tell Python what type a variable is - it figures it out automatically when you assign a value. This makes Python beginner-friendly because you can just write age = 25 instead of int age = 25 like in some other languages.

Why it matters: Understanding types prevents bugs (like trying to add a number to text), helps you choose the right operations for your data, and makes your code more efficient and readable.

Python's Built-in Data Types

Python has several built-in data types. In this lesson, we focus on the primitive types - the basic building blocks:

Category Type Example Description
Numeric int 42, -7, 0 Whole numbers (no decimals)
float 3.14, -0.5 Decimal numbers
complex 3+4j Complex numbers (advanced)
Text str "Hello", 'World' Text (sequences of characters)
Boolean bool True, False Logical values
None NoneType None Absence of value
Coming Soon: In Module 4, we will cover collection types (lists, tuples, dictionaries, sets) which can hold multiple values.

You can check a value's type using the type() function:

# Check the type of any value
print(type(42))        # 
print(type(3.14))      # 
print(type("Hello"))   # 
print(type(True))      # 
print(type(None))      # 
02

Integers (int)

Integers are whole numbers without decimal points. They can be positive, negative, or zero. You use them constantly for counting, indexing, and calculations.

Creating Integers

Just assign a whole number to a variable. No quotes needed!

# Creating integer variables
age = 25
year = 2026
temperature = -10
count = 0

# Python knows these are integers
print(type(age))  # 

Common Use Cases

  • Age: age = 25
  • Year: year = 2026
  • Items in cart: items = 3
  • Temperature: temp = -5
  • Score: points = 100
  • Index/position: index = 0

Large Numbers

Python handles arbitrarily large integers - there is no limit! Use underscores (_) as visual separators for readability:

# Use underscores for readability (Python ignores them)
population = 8_000_000_000        # 8 billion
bank_balance = 1_234_567_890      # Easy to read!
tiny = 0                           # Zero is also an integer

# Python handles huge numbers automatically
huge = 10 ** 100  # 1 followed by 100 zeros (a googol!)
print(huge)       # Works perfectly - no overflow!
Beginner Tip: In many languages, integers have a maximum size (like 2 billion). Python has no such limit - it automatically handles numbers of any size!

Different Number Systems

Python supports binary (base 2), octal (base 8), and hexadecimal (base 16):

# Binary (base 2) - starts with 0b
binary = 0b1010       # = 10 in decimal
print(binary)         # 10

# Octal (base 8) - starts with 0o
octal = 0o17          # = 15 in decimal
print(octal)          # 15

# Hexadecimal (base 16) - starts with 0x
hexadecimal = 0xFF    # = 255 in decimal
print(hexadecimal)    # 255

# Convert decimal to other bases
print(bin(10))        # '0b1010'
print(oct(15))        # '0o17'
print(hex(255))       # '0xff'
When to use: Binary is common when working with low-level data (files, network). Hexadecimal is used for colors (#FF5733) and memory addresses.

Common Integer Operations

Python supports all standard arithmetic operations on integers, just like a calculator! You can add, subtract, multiply, divide, and more. The example below shows two variables a and b, and demonstrates every arithmetic operator you'll commonly use. Pay special attention to the difference between regular division (/) which gives decimals, and floor division (//) which gives whole numbers:

# Basic arithmetic
a = 10
b = 3

print(a + b)   # 13 (addition)
print(a - b)   # 7 (subtraction)
print(a * b)   # 30 (multiplication)
print(a / b)   # 3.333... (division - returns float!)
print(a // b)  # 3 (floor division - returns int)
print(a % b)   # 1 (modulo - remainder)
print(a ** b)  # 1000 (power: 10³)

# Absolute value
print(abs(-42))  # 42
Important: Regular division (/) always returns a float, even for whole numbers: 10 / 2 gives 5.0, not 5. Use floor division (//) if you need an integer result.

Practice Questions: Integers

Problem: Create a variable to store 1 billion (1,000,000,000) using underscores for readability. Then print its type.

Show Solution
billion = 1_000_000_000
print(billion)       # 1000000000
print(type(billion)) # 

Problem: Given 17 cookies to divide among 5 people, calculate: (a) how many cookies each person gets (whole number), and (b) how many cookies are left over.

Show Solution
cookies = 17
people = 5

each_gets = cookies // people  # Floor division
leftover = cookies % people     # Modulo (remainder)

print(f"Each person gets: {each_gets}")  # 3
print(f"Leftover cookies: {leftover}")    # 2

Problem: Convert the binary number 0b11010 to decimal, then convert the decimal number 42 to binary, octal, and hexadecimal.

Show Solution
# Binary to decimal
binary_num = 0b11010
print(binary_num)  # 26 (16+8+2 = 26)

# Decimal to other bases
decimal_num = 42
print(bin(decimal_num))  # 0b101010
print(oct(decimal_num))  # 0o52
print(hex(decimal_num))  # 0x2a
03

Floats (float)

Floats (floating-point numbers) represent decimal numbers. The name comes from how computers store them: the decimal point can "float" to represent very large or very small numbers.

Creating Floats

# Any number with a decimal point is a float
price = 19.99
temperature = -40.5
pi = 3.14159265359
ratio = 0.75

print(type(price))  # 

# Integer divided by integer gives float
result = 10 / 4
print(result)       # 2.5
print(type(result)) # 

Common Use Cases

  • Prices: price = 29.99
  • Measurements: height = 5.9
  • Percentages: rate = 0.15 (15%)
  • Coordinates: lat = 40.7128
  • Scientific data: mass = 9.109e-31

Scientific Notation

For very large or very small numbers, use scientific notation with e:

# Scientific notation: number × 10^exponent
# e23 means × 10²³, e-19 means × 10⁻¹⁹

avogadro = 6.022e23      # 6.022 × 10²³ (Avogadro's number)
planck = 6.626e-34       # 6.626 × 10⁻³⁴ (Planck's constant)
electron_charge = 1.6e-19 # 1.6 × 10⁻¹⁹ (electron charge)

print(avogadro)          # 6.022e+23
print(planck)            # 6.626e-34

# Convert to regular number
speed_of_light = 3e8     # 300,000,000 m/s
print(speed_of_light)    # 300000000.0

The Precision Problem

Critical Warning: Float Precision

Computers store floats in binary, which cannot perfectly represent some decimal numbers. This can cause unexpected results!

# The classic precision problem
result = 0.1 + 0.2
print(result)           # 0.30000000000000004 (not 0.3!)
print(result == 0.3)    # False! Surprising but true

# Why? 0.1 in binary is like 1/3 in decimal - infinite repeating!
# 0.1 (decimal) ≈ 0.0001100110011... (binary) - never exact

Solutions:

# Solution 1: Use round() for display
result = 0.1 + 0.2
print(round(result, 1))  # 0.3

# Solution 2: Use math.isclose() for comparisons
import math
print(math.isclose(result, 0.3))  # True

# Solution 3: Use decimal module for financial calculations
from decimal import Decimal
price1 = Decimal('0.1')
price2 = Decimal('0.2')
print(price1 + price2)   # 0.3 (exact!)
Best Practice: For money/financial calculations, use the Decimal module. For scientific calculations where small errors are acceptable, floats are fine.

Special Float Values

Python floats include special values for representing infinity and undefined results. You probably won't use these often as a beginner, but it's good to know they exist! Infinity (inf) represents a number larger than any other number - useful when you need a value that's guaranteed to be the biggest. NaN (Not a Number) represents an undefined or unrepresentable result, like 0 divided by 0. Here's how they work:

# Infinity
positive_inf = float('inf')
negative_inf = float('-inf')
print(positive_inf > 1e308)  # True (inf is larger than any number)
print(1 / positive_inf)      # 0.0

# Not a Number (NaN) - result of undefined operations
nan = float('nan')
print(nan == nan)            # False! NaN is not equal to itself
import math
print(math.isnan(nan))       # True (proper way to check)

Useful Float Functions

The math module provides essential functions for working with floats. First, import it at the top of your code:

import math

value = 3.7

Rounding: Use round() to round to the nearest value. You can specify decimal places as a second argument:

print(round(value))       # 4 (to nearest integer)
print(round(3.14159, 2))  # 3.14 (2 decimal places)

Floor and Ceiling: floor() always rounds down, ceil() always rounds up, and trunc() removes the decimal part. Use ceil() when you need "at least" - like calculating how many buses for 47 people (if each holds 20): ceil(47/20) = 3, not 2:

print(math.floor(value))  # 3 (round down)
print(math.ceil(value))   # 4 (round up)
print(math.trunc(value))  # 3 (remove decimal part)

Absolute Value: Use abs() to get the positive version of any number:

print(abs(-3.7))          # 3.7

Check Special Values: Use these functions to detect infinity and NaN values:

print(math.isfinite(3.14))      # True
print(math.isinf(float('inf'))) # True
print(math.isnan(float('nan'))) # True

Practice Questions: Floats

Problem: Express the speed of light (300,000,000 m/s) and the size of a hydrogen atom (0.000000000053 meters) using scientific notation in Python.

Show Solution
speed_of_light = 3e8      # 3 x 10^8
hydrogen_size = 5.3e-11   # 5.3 x 10^-11

print(f"Speed of light: {speed_of_light} m/s")
print(f"Hydrogen atom: {hydrogen_size} m")

Problem: Explain why 0.1 + 0.2 == 0.3 returns False and show how to properly compare floats using round() or math.isclose().

Show Solution
import math

# The problem
print(0.1 + 0.2)           # 0.30000000000000004
print(0.1 + 0.2 == 0.3)    # False!

# Solution 1: round()
print(round(0.1 + 0.2, 10) == round(0.3, 10))  # True

# Solution 2: math.isclose() (preferred)
print(math.isclose(0.1 + 0.2, 0.3))  # True

Problem: Calculate the total cost of 3 items priced at $19.99 each using both float and Decimal. Show why Decimal is better for money.

Show Solution
from decimal import Decimal

# Using float (may have precision issues)
price_float = 19.99
total_float = price_float * 3
print(f"Float total: ${total_float}")  # $59.97 (works here, but...)

# Edge case showing float problem
print(0.1 * 3)  # 0.30000000000000004

# Using Decimal (exact)
price_decimal = Decimal("19.99")
total_decimal = price_decimal * 3
print(f"Decimal total: ${total_decimal}")  # $59.97 (always exact)
04

Strings (str)

Strings represent text - sequences of characters. Names, addresses, messages, file contents, JSON data - almost everything you read or display is a string!

Creating Strings

Single or Double Quotes: Both work exactly the same in Python. Choose whichever you prefer, just be consistent:

# Single or double quotes - they're equivalent
name = 'Alice'
greeting = "Hello, World!"

Quotes Inside Strings: Use one type of quote to wrap a string that contains the other type:

# Use one inside the other to include quotes
message1 = "She said 'Hello!'"
message2 = 'He replied "Hi there!"'

Triple Quotes for Multi-line: Use """ or ''' when your text spans multiple lines. The line breaks are preserved:

# Triple quotes for multi-line strings
poem = """Roses are red,
Violets are blue,
Python is awesome,
And so are you!"""

HTML/Code Templates: Triple quotes are perfect for embedding HTML, SQL, or other code that needs formatting:

# Triple quotes also work with single quotes
html = '''

  
    

Hello

'''

Escape Characters

Use backslash (\) for special characters that can't be typed directly.

Newline (\n): Creates a line break in your output:

print("Hello\nWorld")
# Output:
# Hello
# World

Tab (\t): Adds horizontal spacing, useful for aligning columns:

print("Column1\tColumn2")
# Output: Column1    Column2

Escaped Quote (\" or \'): Include quotes inside a string of the same type:

print("She said \"Hi!\"")
# Output: She said "Hi!"

Backslash (\\): Print a literal backslash (common in Windows file paths):

print("C:\\Users\\Data")
# Output: C:\Users\Data

Carriage Return (\r): Returns cursor to the start of the line (overwrites):

print("Line1\rOverwrite")
# Output: Overwrite

Raw Strings

Prefix with r to ignore escape sequences - useful for file paths and regex:

# Raw strings ignore escape sequences
path = r"C:\Users\name\Documents"
print(path)  # C:\Users\name\Documents (no escaping needed!)

# Useful for regular expressions
import re
pattern = r"\d+\.\d+"  # Matches decimal numbers
# Without r: "\\d+\\.\\d+" - much harder to read!

String Operations

Strings support several built-in operations that let you combine, repeat, measure, and search text.

Concatenation: Join strings together using the + operator - like snapping LEGO pieces together:

first = "Hello"
last = "World"
full = first + " " + last  # "Hello World"

Repetition: Use * to repeat a string multiple times - great for creating divider lines or patterns:

line = "-" * 20            # "--------------------"
stars = "*" * 5            # "*****"

Length: len() tells you how many characters are in a string:

message = "Python"
print(len(message))        # 6

Membership Check: Use in to check if one string exists inside another:

print("th" in "Python")    # True
print("x" in "Python")     # False
print("Java" not in "Python")  # True

String Indexing and Slicing

Strings are sequences - ordered collections of characters where each character has a position number called an index. Python counts from 0, so the first character is at index 0:

text = "Python"
#       P y t h o n
#       0 1 2 3 4 5   (positive index)
#      -6-5-4-3-2-1   (negative index)

Positive Indexing: Access characters from the beginning (starts at 0):

print(text[0])      # 'P' (first character)
print(text[1])      # 'y' (second character)

Negative Indexing: Access characters from the end (-1 is the last character):

print(text[-1])     # 'n' (last character)
print(text[-2])     # 'o' (second to last)

Basic Slicing [start:end]: Extract a portion of the string. Note: the end index is exclusive (not included):

print(text[0:3])    # 'Pyt' (characters 0, 1, 2)
print(text[2:5])    # 'tho' (characters 2, 3, 4)

Omitting Start or End: Leave out the start to begin from index 0, or leave out the end to go until the last character:

print(text[:3])     # 'Pyt' (from start to 3)
print(text[3:])     # 'hon' (from 3 to end)
print(text[:])      # 'Python' (entire string)

Step [start:end:step]: Skip characters or reverse the string:

print(text[::2])    # 'Pto' (every 2nd character)
print(text[::-1])   # 'nohtyP' (reversed!)
Remember: In Python, indexing starts at 0, not 1. The first character is at index 0, the second at 1, and so on.

F-Strings (Formatted String Literals)

F-strings (available in Python 3.6 and later) are the modern, preferred way to format strings. They're called "f-strings" because you put an f before the opening quote. Inside the string, you can put any variable or expression inside curly braces {} and Python will replace it with the actual value. This is much cleaner than the old way of adding strings together with +. F-strings are incredibly powerful and you'll use them all the time:

# Basic f-string: prefix with f, use {} for expressions
name = "Priya"
age = 25
message = f"My name is {name} and I'm {age} years old."
print(message)  # My name is Priya and I'm 25 years old.

# Expressions inside {}
print(f"Next year I'll be {age + 1}")  # Next year I'll be 26
print(f"Name uppercase: {name.upper()}")  # Name uppercase: PRIYA

# Calculations
price = 49.99
quantity = 3
print(f"Total: ${price * quantity}")  # Total: $149.97

Number formatting with f-strings:

# Format specifiers come after :
price = 1234.5678

# Decimal places
print(f"Price: ${price:.2f}")      # Price: $1234.57 (2 decimals)

# Thousands separator
print(f"Price: ${price:,.2f}")     # Price: $1,234.57

# Percentage
ratio = 0.856
print(f"Success rate: {ratio:.1%}") # Success rate: 85.6%

# Padding/alignment
print(f"{'left':<10}|")    # left      | (left align, 10 chars)
print(f"{'right':>10}|")   #      right| (right align)
print(f"{'center':^10}|")  #   center  | (center align)

# Leading zeros
num = 42
print(f"{num:05d}")        # 00042 (5 digits with leading zeros)

Common String Methods

Strings come with dozens of built-in methods - special functions that belong to strings. You call them using dot notation: text.upper().

text = "  Hello, World!  "

Case Conversion: Change text to uppercase, lowercase, or title case:

print(text.upper())        # "  HELLO, WORLD!  "
print(text.lower())        # "  hello, world!  "
print(text.title())        # "  Hello, World!  "
print(text.capitalize())   # "  hello, world!  "

Whitespace Handling: Remove extra spaces from strings:

print(text.strip())        # "Hello, World!" (both sides)
print(text.lstrip())       # "Hello, World!  " (left only)
print(text.rstrip())       # "  Hello, World!" (right only)

Finding and Replacing: Search within text and make substitutions:

text2 = "Hello, World!"
print(text2.find("World"))     # 7 (index where found)
print(text2.find("Python"))    # -1 (not found)
print(text2.replace("World", "Python"))  # "Hello, Python!"
print(text2.count("l"))        # 3 (count occurrences)

Checking Content: Verify what type of characters are in a string:

print("hello".isalpha())   # True (all letters)
print("123".isdigit())     # True (all digits)
print("hello123".isalnum()) # True (letters or digits)
print("  ".isspace())      # True (all whitespace)
print("Hello".startswith("He"))  # True
print("Hello".endswith("lo"))    # True

Splitting and Joining: Break strings apart or combine them:

csv = "apple,banana,cherry"
fruits = csv.split(",")    # ['apple', 'banana', 'cherry']
print(fruits)

words = ['Hello', 'World']
sentence = " ".join(words) # "Hello World"
print(sentence)
Immutability: Strings cannot be changed in place. Methods like upper() and replace() return new strings - the original is unchanged.

Practice Questions: Strings

Problem: Given text = "Hello, World!", extract: (a) "Hello", (b) "World", (c) the last character, (d) the string reversed.

Show Solution
text = "Hello, World!"

hello = text[0:5]      # or text[:5]
world = text[7:12]     # "World"
last_char = text[-1]   # "!"
reversed_text = text[::-1]  # "!dlroW ,olleH"

print(hello, world, last_char)
print(reversed_text)

Problem: Create an f-string that displays: "Product: Widget, Price: $29.99, Quantity: 5, Total: $149.95" using variables and format specifiers.

Show Solution
product = "Widget"
price = 29.99
quantity = 5
total = price * quantity

result = f"Product: {product}, Price: ${price:.2f}, Quantity: {quantity}, Total: ${total:.2f}"
print(result)

Problem: Given " HELLO world ", transform it to "Hello World" (title case, no extra spaces) using string methods.

Show Solution
text = "  HELLO world  "

# Chain methods: strip whitespace, then title case
result = text.strip().title()
print(result)  # "Hello World"

# Or step by step:
text = text.strip()   # "HELLO world"
text = text.title()   # "Hello World"
05

Booleans (bool)

Booleans represent truth values: True or False. They are the foundation of decision-making in programming - every if statement relies on booleans!

Creating Booleans

Create boolean variables by assigning True or False directly. Important: The capital letters matter! True works, but true will cause an error. When naming boolean variables, it's a good practice to start with words like is_, has_, can_, or should_ so it's clear the variable holds a yes/no value:

# Direct assignment (note the capital T and F)
is_active = True
is_admin = False
has_permission = True

print(type(is_active))  # 

# Common naming: start with is_, has_, can_, should_
is_valid = True
has_errors = False
can_edit = True
should_retry = False

Booleans from Comparisons

In practice, most booleans come from comparisons rather than direct assignment. When you compare two values, Python returns True or False.

Numeric Comparisons: Compare numbers using >, <, >=, <=, ==, and !=:

print(5 > 3)       # True
print(5 < 3)       # False
print(5 >= 5)      # True
print(5 <= 4)      # False
print(5 == 5)      # True (equality)
print(5 != 3)      # True (not equal)

String Comparisons: Strings are compared alphabetically (by character codes):

print("apple" < "banana")   # True
print("Apple" < "apple")    # True (uppercase < lowercase)

Membership Testing: Use in to check if something exists inside a collection:

print("a" in "apple")       # True
print(3 in [1, 2, 3])       # True
print("x" not in "hello")   # True

Identity Testing: Use is to check if two variables point to the exact same object (not just equal values):

a = [1, 2, 3]
b = a
c = [1, 2, 3]
print(a is b)      # True (same object)
print(a is c)      # False (different objects, same value)
print(a == c)      # True (same value)

Boolean Operations

Combine booleans using logical operators: and, or, and not. These let you build complex conditions from simple ones.

and Operator: Both sides must be True (like "I need a ticket AND an ID to enter"):

print(True and True)    # True
print(True and False)   # False
print(False and False)  # False

or Operator: At least one side must be True (like "I can pay with cash OR card"):

print(True or True)     # True
print(True or False)    # True
print(False or False)   # False

not Operator: Flips True to False and vice versa:

print(not True)         # False
print(not False)        # True

Practical Example with and: Check if someone can drive (must be 18+ AND have a license):

age = 25
has_license = True
can_drive = age >= 18 and has_license
print(can_drive)        # True

Practical Example with or: Check if it's a day off (weekend OR holiday):

is_weekend = False
is_holiday = True
day_off = is_weekend or is_holiday
print(day_off)          # True

Truthiness and Falsiness

Here's something that surprises many beginners: Python can treat any value as a boolean, not just True and False! Values that act like True in conditions are called "truthy", and values that act like False are called "falsy". The simple rule: empty or zero values are falsy, everything else is truthy. This is incredibly useful for writing cleaner code:

Falsy Values
# These all evaluate to False
bool(False)      # False
bool(None)       # False
bool(0)          # False
bool(0.0)        # False
bool("")         # False (empty string)
bool([])         # False (empty list)
bool({})         # False (empty dict)
bool(())         # False (empty tuple)
bool(set())      # False (empty set)
Truthy Values
# Everything else is True
bool(True)       # True
bool(1)          # True
bool(-1)         # True (any non-zero)
bool(3.14)       # True (any non-zero)
bool("hello")    # True (non-empty string)
bool(" ")        # True (space is not empty!)
bool([1, 2])     # True (non-empty list)
bool({"a": 1})   # True (non-empty dict)

Why this matters: You can use any value directly in conditions! Instead of writing if len(name) > 0:, you can simply write if name:. This makes your code cleaner, shorter, and more "Pythonic" (following Python's style conventions). Here are some practical examples:

# Practical use of truthiness
name = ""
if name:
    print(f"Hello, {name}!")
else:
    print("Name is empty!")  # This runs

# Check if list has items
items = [1, 2, 3]
if items:
    print(f"Found {len(items)} items")  # This runs
else:
    print("No items found")

# Short-circuit with 'or' for defaults
username = ""
display_name = username or "Guest"
print(display_name)  # "Guest" (because username is falsy)

Practice Questions: Booleans

Problem: Predict the boolean value of: bool(0), bool(""), bool("False"), bool([]), bool([0])

Show Solution
print(bool(0))        # False (zero is falsy)
print(bool(""))       # False (empty string is falsy)
print(bool("False"))  # True (non-empty string is truthy!)
print(bool([]))       # False (empty list is falsy)
print(bool([0]))      # True (non-empty list is truthy)

Problem: What gets printed? print(False and print("A")) and print(True or print("B"))

Show Solution
# Short-circuit: Python stops early if result is determined

print(False and print("A"))  
# Output: False
# "A" never prints! 'and' stops at first False

print(True or print("B"))
# Output: True  
# "B" never prints! 'or' stops at first True

Problem: Write a function that takes an optional name parameter and returns a greeting. If no name is given (or empty string), use "Stranger".

Show Solution
def greet(name=""):
    # Use 'or' for default (works for empty/None)
    display_name = name or "Stranger"
    return f"Hello, {display_name}!"

print(greet("Alice"))   # Hello, Alice!
print(greet(""))        # Hello, Stranger!
print(greet())          # Hello, Stranger!
06

None Type

None is Python's null value. It represents the absence of a value - not zero, not empty, not False, but truly "nothing".

What is None?

None is a special singleton object in Python - there's only one None in the entire program, and it represents "nothing" or "no value". It's the only value of the NoneType type. Think of it as an empty box that's intentionally left empty, rather than a box containing a zero or an empty string. This distinction is important because None is different from all other values:

# None represents "no value" or "nothing"
result = None
print(result)           # None
print(type(result))     # 

# None is NOT the same as:
# - 0 (zero is a number)
# - "" (empty string is still a string)
# - False (False is a boolean)
# - [] (empty list is still a list)

Checking for None

Important: Always use is None or is not None, never == None. This is a Python style guideline (PEP 8).

Here's the correct way to check if a variable contains None. We use is instead of == because is checks if two things are the exact same object in memory, which is more reliable for None. The == operator checks equality, which can be customized by objects (potentially leading to unexpected results):

# Correct way to check for None
value = None

if value is None:
    print("Value is None")

if value is not None:
    print("Value has a value")

# Why not use ==?
# Because 'is' checks identity (same object)
# while '==' checks equality (could be overridden)
print(value is None)   # True (recommended)
print(value == None)   # True (works but not recommended)

Common Use Cases for None

None appears frequently in Python code, and understanding when to use it will make you a better programmer. Here are the four most common scenarios where you'll encounter or use None. Pay attention to these patterns because they're used everywhere in real Python code:

When a function doesn't have a return statement (or has return without a value), Python automatically returns None. This happens with functions that just perform an action (like printing) without calculating a result. If you try to save the result, you'll get None:

# This function prints but doesn't return anything
def greet(name):
    print(f"Hello, {name}!")
    # No return statement

result = greet("Priya")  # Prints: Hello, Priya!
print(result)             # None (because greet() has no return)

When a function searches for something but might not find it, returning None is a clean way to signal "not found". This is better than returning -1 or an empty string because None clearly means "nothing" rather than a valid value. The dictionary .get() method uses this pattern:

# Function returns None when user isn't found
def find_user(user_id):
    users = {1: "Alice", 2: "Bob"}
    return users.get(user_id)  # Returns None if key not found

user = find_user(999)  # Looking for user ID 999
if user is None:
    print("User not found!")  # This runs because 999 doesn't exist

Using None as a default parameter value is a common pattern when you want to detect if an argument was provided or not. Inside the function, you check if param is None and then assign a default value. This is especially useful when the actual default is computed or mutable:

# port=None means "use default if not specified"
def connect(host, port=None):
    if port is None:
        port = 8080  # Use default port
    print(f"Connecting to {host}:{port}")

connect("localhost")        # Output: Connecting to localhost:8080
connect("localhost", 3000)  # Output: Connecting to localhost:3000

Sometimes you need to create a variable before you know its value - maybe it will be set later in your code, or only under certain conditions. Using None as a placeholder clearly shows "this variable exists but hasn't been given a real value yet". Later, you can check if it was ever assigned:

# Initialize as None - we'll set it later
data = None

# Later in the code, maybe inside a condition:
# data = fetch_from_database()

# Check if data was ever assigned a real value
if data is not None:
    print("Processing data...")
else:
    print("No data to process")
None is Falsy: In boolean context, None evaluates to False. So if not value: will be True when value is None, but also when it's 0, "", or []. Use if value is None: when you specifically want to check for None.

Practice Questions: None Type

Problem: Why should you use is None instead of == None? Write both versions.

Show Solution
value = None

# Correct way (identity check)
if value is None:
    print("Value is None")

# Works but not recommended
if value == None:
    print("Value equals None")

# Why 'is' is better:
# 1. Faster (compares memory addresses)
# 2. Safer (== can be overridden by classes)
# 3. More Pythonic (PEP 8 recommends it)

Problem: A user's age could be None (not provided), 0 (newborn), or a positive number. Write code to handle all three cases differently.

Show Solution
def describe_age(age):
    if age is None:
        return "Age not provided"
    elif age == 0:
        return "Newborn baby"
    else:
        return f"{age} years old"

print(describe_age(None))  # Age not provided
print(describe_age(0))     # Newborn baby
print(describe_age(25))    # 25 years old

Problem: Write a function find_item(items, target, default=None) that returns the target if found, otherwise returns the default value.

Show Solution
def find_item(items, target, default=None):
    for item in items:
        if item == target:
            return item
    return default  # Return default if not found

numbers = [1, 2, 3, 4, 5]

print(find_item(numbers, 3))           # 3 (found)
print(find_item(numbers, 10))          # None (not found)
print(find_item(numbers, 10, -1))      # -1 (custom default)
print(find_item(numbers, 10, "N/A"))   # "N/A"
07

Type Checking

Sometimes you need to know what type a value is. Python provides two main ways to check types.

Using type()

The type() function returns the exact type of any value. It's like asking Python "What kind of thing is this?" This is useful for debugging and understanding your data. You can also compare the result with a type name to check if something is a specific type. The output shows <class 'typename'> which tells you the type:

# type() returns the exact type
print(type(42))         # 
print(type(3.14))       # 
print(type("hello"))    # 
print(type(True))       # 
print(type(None))       # 
print(type([1, 2, 3]))  # 

# Comparing types
x = 42
print(type(x) == int)   # True
print(type(x) == str)   # False

Using isinstance() (Recommended)

isinstance(value, type) is the preferred way to check types because it's more flexible. It returns True if the value is of the specified type. Two key advantages: (1) you can check multiple types at once by passing a tuple, and (2) it understands inheritance - for example, booleans are technically a type of integer in Python, and isinstance() knows this:

# isinstance(value, type) - returns True or False
x = 42
print(isinstance(x, int))    # True
print(isinstance(x, str))    # False

# Check multiple types at once
value = 3.14
print(isinstance(value, (int, float)))  # True (is int OR float)

# Boolean is a subclass of int!
print(isinstance(True, bool))  # True
print(isinstance(True, int))   # True (bool inherits from int)
print(type(True) == int)       # False (exact type is bool)

# This is why isinstance() is preferred for type checking
Best Practice: Use isinstance() for type checking. It is more flexible and handles edge cases better than comparing with type().

Practical Examples

Type checking is commonly used to validate function inputs (making sure you received the right kind of data before processing it) and to handle different types dynamically (doing different things based on what type of data you receive). Here are two real-world patterns you'll see and use frequently:

# Validate function input
def calculate_area(length, width):
    # Check if inputs are numbers
    if not isinstance(length, (int, float)):
        raise TypeError("Length must be a number")
    if not isinstance(width, (int, float)):
        raise TypeError("Width must be a number")
    return length * width

This function acts like a security guard for your calculations. Before doing any math, it checks whether both length and width are actually numbers (either integers or floats). If someone tries to pass a string like "5" instead of the number 5, the function raises a TypeError with a helpful message. This prevents confusing bugs later in your program.

print(calculate_area(5, 3))     # 15
print(calculate_area(5.5, 3.2)) # 17.6
# calculate_area("5", 3)        # Raises TypeError

Here we test our protected function. It works perfectly with integers (5 × 3 = 15) and floats (5.5 × 3.2 = 17.6). But if we try to sneak in a string "5", the function catches it and raises an error instead of producing unexpected results or crashing mysteriously.

# Handle different types
def process_data(data):
    if isinstance(data, str):
        return data.upper()
    elif isinstance(data, (int, float)):
        return data * 2
    elif isinstance(data, list):
        return len(data)
    else:
        return "Unknown type"

This function is like a smart assistant that adapts its behavior based on what you give it. If you pass a string, it converts it to uppercase. If you pass a number, it doubles it. If you pass a list, it tells you how many items are in it. This pattern is incredibly useful when building flexible functions that need to handle multiple types of input.

print(process_data("hello"))  # "HELLO"
print(process_data(5))        # 10
print(process_data([1,2,3]))  # 3

Watch the smart function in action: "hello" becomes "HELLO" (uppercase), 5 becomes 10 (doubled), and the list [1, 2, 3] returns 3 (its length). One function, three different behaviors—all determined by type checking!

Practice Questions: Type Checking

Problem: Check if the value 3.14 is a number (int or float) using both type() and isinstance(). Which is better?

Show Solution
value = 3.14

# Using type() - must check each type separately
is_number_type = type(value) == int or type(value) == float
print(is_number_type)  # True

# Using isinstance() - can check multiple types at once
is_number_isinstance = isinstance(value, (int, float))
print(is_number_isinstance)  # True

# isinstance() is better because:
# 1. Cleaner syntax for multiple types
# 2. Works with inheritance
# 3. More Pythonic

Problem: Write a function describe(value) that returns "text" for strings, "number" for int/float, "yes/no" for bool, and "unknown" for anything else.

Show Solution
def describe(value):
    # Check bool FIRST (bool is subclass of int!)
    if isinstance(value, bool):
        return "yes/no"
    elif isinstance(value, str):
        return "text"
    elif isinstance(value, (int, float)):
        return "number"
    else:
        return "unknown"

print(describe("hello"))  # text
print(describe(42))       # number
print(describe(3.14))     # number
print(describe(True))     # yes/no
print(describe([1,2,3]))  # unknown

Problem: Explain why isinstance(True, int) returns True and what problems this can cause when type checking.

Show Solution
# Bool is a SUBCLASS of int in Python!
print(isinstance(True, int))   # True!
print(isinstance(False, int))  # True!

# This can cause problems:
def process_number(n):
    if isinstance(n, int):
        return n * 2
    return None

print(process_number(5))     # 10 (correct)
print(process_number(True))  # 2 (oops! True = 1)

# Solution: check bool BEFORE int
def process_number_safe(n):
    if isinstance(n, bool):  # Check bool first!
        return None
    if isinstance(n, int):
        return n * 2
    return None
08

Type Conversion (Casting)

Converting data from one type to another is called type conversion or casting. Python provides built-in functions for this.

Conversion Functions

Function Converts To Example Result
int() Integer int("42") 42
float() Float float("3.14") 3.14
str() String str(42) "42"
bool() Boolean bool(1) True

String to Number

Converting strings to numbers is one of the most common operations you'll do, especially when handling user input (the input() function always returns a string!) or reading data from files. Use int() to convert to a whole number, or float() for decimal numbers.

# String to integer
age_str = "25"
age_int = int(age_str)
print(age_int + 5)        # 30

The int() function takes a string containing digits and converts it to an integer. Once converted, you can perform math operations on it. Without conversion, "25" + 5 would cause an error.

# String to float
price_str = "19.99"
price_float = float(price_str)
print(price_float * 2)    # 39.98

Use float() when the string contains a decimal point. This is common for prices, measurements, and any data that needs decimal precision.

# Float string to int (must go through float first!)
# int("3.14")             # ERROR! Can't convert directly
int(float("3.14"))        # 3 (works! First float, then int)

Watch out: You cannot convert a string like "3.14" directly to an integer - Python will raise an error. You must first convert to float, then to int. This is a common gotcha!

# Converting from different number bases
binary_str = "1010"
decimal = int(binary_str, 2)  # Convert from base 2 (binary)
print(decimal)            # 10

hex_str = "FF"
decimal = int(hex_str, 16)    # Convert from base 16 (hexadecimal)
print(decimal)            # 255

The int() function accepts an optional second argument specifying the base of the number. Use 2 for binary, 8 for octal, and 16 for hexadecimal. This is useful when working with color codes, file permissions, or low-level data.

Number to String

Convert numbers to strings when you need to combine them with other text or display them to users. Remember: you can't concatenate a number directly to a string with + (Python will give you an error). You must first convert the number using str().

# Integer to string
age = 25
age_str = str(age)
print("I am " + age_str + " years old")

The str() function converts any value to its string representation. This is essential when you want to concatenate numbers with text using the + operator. Without conversion, "I am " + 25 would cause a TypeError.

# Float to string
pi = 3.14159
pi_str = str(pi)
print(pi_str)             # "3.14159"

Converting a float to a string preserves all the decimal digits. The result is a text representation of the number that you can combine with other strings or display to users.

# Formatted conversion with f-strings (recommended!)
price = 19.99
formatted = f"${price:.2f}"  # .2f means 2 decimal places
print(formatted)          # "$19.99"

For formatted output, f-strings are the better choice over str(). They automatically handle the conversion and give you powerful formatting options like controlling decimal places (:.2f), adding thousand separators (:,), and padding with zeros.

Between Float and Integer

Converting between floats and integers has some important behaviors that often surprise beginners. Critical point: int() doesn't round - it truncates, meaning it just chops off the decimal part and keeps the whole number.

# Float to int (truncates, doesn't round!)
print(int(3.7))           # 3 (not 4!)
print(int(3.2))           # 3
print(int(-3.7))          # -3 (towards zero)

Notice that int(3.7) gives 3, not 4. It simply removes everything after the decimal point. For negative numbers, it moves towards zero, so int(-3.7) gives -3, not -4. This is called truncation.

# If you want proper rounding, use these functions:
import math
print(round(3.7))         # 4 (nearest integer)
print(math.floor(3.7))    # 3 (always rounds down)
print(math.ceil(3.2))     # 4 (always rounds up)

If you need actual rounding behavior, Python provides three options: round() rounds to the nearest integer, math.floor() always rounds down (towards negative infinity), and math.ceil() always rounds up (towards positive infinity). Choose based on your needs.

# Int to float
x = 5
y = float(x)
print(y)                  # 5.0
print(type(y))            # 

Converting an integer to a float is straightforward - it just adds .0 to the number. This is useful when you need decimal precision in calculations or when a function expects a float value.

Boolean Conversion

Convert values to booleans using bool(), which follows the truthiness rules we learned earlier (empty/zero = False, everything else = True).

# Converting values to boolean
print(bool(1))            # True
print(bool(0))            # False
print(bool("hello"))      # True
print(bool(""))           # False
print(bool([1, 2]))       # True
print(bool([]))           # False

The bool() function converts any value to True or False. The rule is simple: zero, empty strings, empty lists, and None become False. Everything else becomes True. This is the same truthiness behavior used in if statements.

# Boolean to number
print(int(True))          # 1
print(int(False))         # 0
print(float(True))        # 1.0

You can also convert booleans to numbers! True becomes 1 and False becomes 0. This works with both int() and float(). This behavior exists because in Python, bool is actually a subtype of int.

# Useful trick: counting True values with sum()!
results = [True, False, True, True, False]
print(sum(results))       # 3 (counts how many are True)

Here's a powerful trick: since True equals 1 and False equals 0, you can use sum() to count how many True values are in a list! This is commonly used for counting items that match a condition.

Common Conversion Errors

Not all conversions are valid! If you try to convert something that doesn't make sense (like the word "hello" to a number), Python will raise a ValueError and your program will crash. This happens a lot when processing user input - users might type "abc" when you expected a number. There are two ways to handle this safely: try/except (attempt the conversion and catch the error) or check first (verify the string looks like a number before converting):

# These will raise ValueError:
# int("hello")            # Can't convert non-numeric string
# int("3.14")             # Can't convert float string directly
# float("hello")          # Can't convert non-numeric string

These are the most common conversion mistakes. int("hello") fails because "hello" has no numeric meaning. Surprisingly, int("3.14") also fails—you can't convert a decimal string directly to an integer (you'd need to use int(float("3.14")) first). And float("hello") fails for the same reason as the first example.

# Safe conversion with error handling
user_input = "abc"
try:
    number = int(user_input)
except ValueError:
    print("That's not a valid number!")

The try/except approach is like wearing a safety net. You try to do the conversion, and if it fails, Python jumps to the except block instead of crashing. This is the most common pattern for handling user input because you never know what users will type.

# Or check first
if user_input.isdigit():
    number = int(user_input)
else:
    print("Please enter a number")

The "check first" approach uses .isdigit() to verify the string contains only digits before attempting conversion. This is cleaner for simple cases, but note that it won't work for negative numbers or decimals (since "-5" and "3.14" contain non-digit characters).

Practice Questions: Type Conversion

Problem: Convert: (a) "42" to int, (b) 3.7 to int, (c) 42 to string, (d) "3.14" to float.

Show Solution
# (a) String to int
a = int("42")
print(a, type(a))  # 42 

# (b) Float to int (truncates, doesn't round!)
b = int(3.7)
print(b, type(b))  # 3 

# (c) Int to string
c = str(42)
print(c, type(c))  # "42" 

# (d) String to float
d = float("3.14")
print(d, type(d))  # 3.14 

Problem: Write a function get_number(text) that converts a string to a number (int or float as appropriate) or returns None if conversion fails.

Show Solution
def get_number(text):
    try:
        # Try int first
        if '.' not in text:
            return int(text)
        else:
            return float(text)
    except ValueError:
        return None

print(get_number("42"))     # 42 (int)
print(get_number("3.14"))   # 3.14 (float)
print(get_number("hello"))  # None
print(get_number("-5"))     # -5 (int)

Problem: Why does int("3.14") fail? How would you convert the string "3.14" to the integer 3?

Show Solution
# This FAILS:
# int("3.14")  # ValueError!
# Python won't convert a decimal string directly to int

# Solution: Chain the conversions
text = "3.14"
result = int(float(text))  # First to float, then to int
print(result)  # 3

# Why it works:
# Step 1: float("3.14") -> 3.14
# Step 2: int(3.14) -> 3 (truncates decimal)

# Alternative with rounding:
rounded = round(float("3.14"))  # 3 (rounds to nearest)
print(rounded)

Key Takeaways

Integers (int)

Whole numbers of any size. Use underscores for readability. Floor division (//) returns int.

Floats (float)

Decimal numbers. Watch for precision issues! Use Decimal for money.

Strings (str)

Text data. Immutable. Use f-strings for formatting. Rich set of methods available.

Booleans (bool)

True or False. Understand truthiness - empty/zero values are falsy.

None

Represents "no value". Check with is None. Used for optional values and missing data.

Type Conversion

Use int(), float(), str(), bool(). Handle errors with try/except.

Quick Reference
# Type checking
type(x)                    # Get type
isinstance(x, int)         # Check type (preferred)
isinstance(x, (int, float)) # Check multiple types

# Type conversion
int("42")                  # String to int
float("3.14")              # String to float
str(42)                    # Number to string
bool(x)                    # Any to boolean

# Common checks
x is None                  # Check for None
x is not None              # Check for not None
if x:                      # Truthy check
if not x:                  # Falsy check
10

Interactive Demo

Experiment with Python data types in real-time! Try different values and see how type conversion works.

Type Converter

Enter a value and see how Python converts it to different types.

type() = <class 'str'>
int() = 42
float() = 42.0
str() = "42"
bool() = True
len() = 2

Truthiness Tester

Test which values are truthy or falsy in Python.

FALSY

The value 0 is falsy because zero is considered "empty" in boolean context.

Knowledge Check

Quick Quiz

Test what you've learned about Python data types

1 What is the output of type(3.0)?
2 What does int(3.9) return?
3 Which of these is falsy in Python?
4 How should you check if a variable equals None?
5 Why does isinstance(True, int) return True?
6 What happens with int(\"3.14\")?
Answer all questions to check your score