Python Variables: Your First Step to Programming Mastery
Why Variables Matter (And Why You Should Care)
Imagine organizing a massive library. You need labels, tags, and a system to find anything instantly. Variables are your labeling system for data in programming.
After diving deep into Python this week, I realized understanding variables isn't about memorization, it's about building mental models that support everything you'll learn next.
What IS a Variable? (The Truth About Memory)
The Beginner Explanation
A variable is a named container storing a value in memory.
The Real Story (This Changes Everything!)
Variables in Python are NOT boxes that hold values.
Variables are LABELS (references) that point to objects in memory.
# When you write this:
age = 25
# Python actually does this:
# 1. Creates an integer object with value 25 in memory
# 2. Creates a label 'age' that points to that object
# 3. If you reassign age, it points to a DIFFERENT object
Seeing Memory in Action
Python gives you a superpower: the id() function shows memory addresses!
x = 100
print(f"Value: {x}")
print(f"Memory address: {id(x)}")
# Every object has:
# - Identity: unique memory address (id())
# - Type: data type (type())
# - Value: the data itself
Output:
Value: 100
Memory address: 140715830258480 # Your address will differ
Everything is an Object
In Python, EVERYTHING is an object—even simple numbers:
python
x = 5
print(type(x)) # <class 'int'>
print(type(type(x))) # <class 'type'> - types are objects too!
# Even functions are objects!
print(type(print)) # <class 'builtin_function_or_method'>
This "everything is an object" philosophy is core to Python's design.
Core Data Types
Python has several built-in types you'll use constantly:
1. Integers (int) — Whole Numbers
age = 30
population = 7_900_000_000 # Underscores for readability!
negative = -42
print(type(age)) # <class 'int'>
2. Floats (float) — Decimal Numbers
height = 5.9
temperature = -3.5
pi = 3.14159
print(type(height)) # <class 'float'>
3. Strings (str) — Text
name = "Alice"
message = 'Hello, World!' # Single or double quotes
multiline = """This spans
multiple lines"""
print(type(name)) # <class 'str'>
4. Booleans (bool) — True or False
is_student = False
has_license = True
print(type(is_student)) # <class 'bool'>
⚠️ Critical: Integer + Float = Float
num1 = 10 # int
num2 = 3.5 # float
result = num1 + num2
print(result) # 13.5
print(type(result)) # <class 'float'> - automatically converted!
Key insight: Python uses implicit type coercion to preserve precision.
Division with a single “/” Always Returns Float
# Regular division ALWAYS gives float
result = 10 / 2
print(result) # 5.0 (float, not 5!)
print(type(result)) # <class 'float'>
# Floor division for integers
result = 10 // 3
print(result) # 3 (int)
Variable Naming Conventions
Good names = self-documenting code. Bad names = debugging nightmares.
The Rules
# ✅ VALID
user_name = "john"
_private_var = 42
user2 = "jane"
CONSTANT_VALUE = 100
# ❌ INVALID
2nd_user = "bob" # Can't start with number
user-name = "alice" # No hyphens
for = 10 # Reserved keyword
user name = "eve" # No spaces
Python Convention: snake_case
# ✅ Python style (snake_case)
total_price = 99.99
user_age = 28
is_active = True
# ❌ Don't use (camelCase is for JavaScript/Java)
totalPrice = 99.99
userAge = 28
Constants Use UPPER_CASE
PI = 3.14159
MAX_CONNECTIONS = 100
API_KEY = "secret123" # Though better in env vars!
Pro tip: Python doesn't enforce constants, but naming convention signals intent.
Variable Assignment & Multiple Assignment
Basic Assignment
x = 10
y = 20
x = 15 # Reassignment
Multiple Assignment
# Assign multiple variables at once
a, b, c = 1, 2, 3
print(a, b, c) # 1 2 3
# Swap without temporary variable!
x, y = 5, 10
x, y = y, x # Mind = blown 🤯
print(x, y) # 10 5
This is called tuple unpacking and it's incredibly powerful:
# Extended unpacking with *
first, *middle, last = [1, 2, 3, 4, 5]
print(first) # 1
print(middle) # [2, 3, 4]
print(last) # 5
🚨 Critical Concept: Variables are References, Not Boxes
This is THE concept that separates beginners from intermediate programmers.
The Box Analogy is WRONG
# What beginners think happens:
a = [1, 2, 3] # Box 'a' contains [1, 2, 3]
b = a # Box 'b' gets a COPY of [1, 2, 3]
# What ACTUALLY happens:
a = [1, 2, 3] # Create a list object, 'a' points to it
b = a # 'b' points to THE SAME object
Proof: Modifying One Changes Both
a = [1, 2, 3]
b = a # Both point to same object!
b.append(4)
print(a) # [1, 2, 3, 4] - a changed too!
print(b) # [1, 2, 3, 4]
# Verify they're the same object
print(id(a) == id(b)) # True
Visualization:
a ────┐
↓
[1, 2, 3, 4] ← Single object in memory
↑
b ────┘
Why Don't Integers Behave This Way?
x = 10
y = x
y = 20
print(x) # 10 - x didn't change!
This leads us to...
Mutable vs Immutable Objects
This explains EVERYTHING about Python's behavior!
Immutable Types (Cannot Change After Creation)
int,float,str,tuple,bool,frozenset
python
x = 10
print(id(x)) # Let's say 140715830258480
x = 11 # Creates NEW object, doesn't modify old one
print(id(x)) # Different ID! 140715830258512
Mutable Types (Can Be Modified In-Place)
list,dict,set
my_list = [1, 2, 3]
print(id(my_list)) # Let's say 2131234567890
my_list.append(4) # Modifies SAME object
print(id(my_list)) # SAME ID! 2131234567890
Why This Matters
# Immutable example
def try_to_change(x):
x = 999 # Creates new local variable
num = 10
try_to_change(num)
print(num) # 10 - unchanged!
# Mutable example
def try_to_change_list(lst):
lst.append(999) # Modifies original object!
numbers = [1, 2, 3]
try_to_change_list(numbers)
print(numbers) # [1, 2, 3, 999] - changed!
The is vs == Operator
Understanding references unlocks this:
list1 = [1, 2, 3]
list2 = [1, 2, 3]
list3 = list1
# == compares VALUES
print(list1 == list2) # True - same values
# is compares IDENTITY (same object?)
print(list1 is list2) # False - different objects
print(list1 is list3) # True - same object!
print(id(list1)) # e.g., 12345678
print(id(list2)) # e.g., 87654321 (different!)
print(id(list3)) # e.g., 12345678 (same as list1!)
Python's Small Integer Cache
Python optimizes by reusing small integers (-5 to 256):
a = 256
b = 256
print(a is b) # True - Python reuses same object!
a = 257
b = 257
print(a is b) # False - separate objects
# (might be True in interactive mode)
Why? Memory efficiency—small numbers are used constantly.
Type Conversion (Casting)
Convert between types explicitly:
# int → float
x = 10
y = float(x)
print(y) # 10.0
# float → int (truncates, doesn't round!)
x = 9.99
y = int(x)
print(y) # 9 (not 10!)
# int → str
x = 42
y = str(x)
print(y) # "42" (string)
# str → int
x = "100"
y = int(x)
print(y) # 100 (integer)
⚠️ Invalid Conversions Raise Errors
# This will crash!
# int("hello") # ValueError
# Always validate first
user_input = "123"
if user_input.isdigit():
number = int(user_input)
print(number) # 100
Variable Scope
Where can you access a variable?
Local Scope
python
def my_function():
local_var = "I'm local"
print(local_var) # ✅ Works
my_function()
# print(local_var) # ❌ NameError!
Global Scope
python
global_var = "I'm global"
def my_function():
print(global_var) # ✅ Can read global
my_function()
print(global_var) # ✅ Works here too
Modifying Global Variables
python
counter = 0
def increment():
global counter # Must declare!
counter += 1
increment()
print(counter) # 1
The LEGB Rule
Python searches for variables in this order:
Local - inside current function
Enclosing - in any enclosing functions
Global - module level
Built-in - Python's built-in namespace
python
x = "global"
def outer():
x = "enclosing"
def inner():
x = "local"
print(x) # "local"
inner()
print(x) # "enclosing"
outer()
print(x) # "global"
Modern Python: Type Hints (Python 3.5+)
Not enforced at runtime, but improves readability and catches errors with tools:
# Basic type hints
name: str = "Alice"
age: int = 30
height: float = 5.9
is_student: bool = True
# Function with type hints
def greet(name: str, age: int) -> str:
return f"Hello {name}, you are {age} years old"
# Collections (Python 3.9+)
numbers: list[int] = [1, 2, 3]
scores: dict[str, int] = {"Alice": 95}
# Optional values (can be None)
from typing import Optional
middle_name: Optional[str] = None
Why use them?
IDEs give better autocomplete
Static type checkers (mypy) catch bugs
Self-documenting code
Required in many professional codebases
Best Practices: Production-Ready Code
1. Descriptive Names Over Brevity
# ❌ Don't
t = 99.99
u = "John"
c = 0
# ✅ Do
total_price = 99.99
username = "John"
item_count = 0
2. Avoid Single Letters (Except Loops)
# ❌ Unclear
for x in items:
process(x)
# ✅ Clear
for item in items:
process(item)
# ✅ OK for math/coordinates
x, y = calculate_position()
3. Use Constants for Magic Numbers
# ❌ What does 86400 mean?
seconds = days * 86400
# ✅ Clear intent
SECONDS_PER_DAY = 86400
seconds = days * SECONDS_PER_DAY
4. Minimize Global Variables
# ❌ Risky
total = 0
def add_to_total(x):
global total
total += x
# ✅ Better - use return values
def add_to_total(current_total, x):
return current_total + x
total = add_to_total(total, 5)
Common Pitfalls (Learn from My Mistakes!)
Pitfall #1: Mutable Default Arguments
# ❌ THIS WILL SURPRISE YOU
def add_item(item, items=[]):
items.append(item)
return items
print(add_item(1)) # [1]
print(add_item(2)) # [1, 2] - Wait, what?!
print(add_item(3)) # [1, 2, 3] - They all share the same list!
# ✅ Correct approach
def add_item(item, items=None):
if items is None:
items = []
items.append(item)
return items
print(add_item(1)) # [1]
print(add_item(2)) # [2] - Works as expected!
Why? Default arguments are evaluated ONCE when function is defined!
Pitfall #2: Copying Mutable Objects
# ❌ Not a real copy!
original = [1, 2, 3]
copy = original
copy.append(4)
print(original) # [1, 2, 3, 4] - Changed!
# ✅ Shallow copy
import copy
original = [1, 2, 3]
copy = original.copy() # or list(original)
copy.append(4)
print(original) # [1, 2, 3] - Unchanged!
# ✅ Deep copy (for nested structures)
original = [[1, 2], [3, 4]]
deep = copy.deepcopy(original)
Pitfall #3: Shadowing Built-ins
# ❌ Don't do this!
list = [1, 2, 3] # Now list() doesn't work!
str = "hello" # Now str() doesn't work!
# ✅ Use descriptive names
items_list = [1, 2, 3]
name_str = "hello"
Pitfall #4: Integer Division Surprise
# ❌ Expecting integer result
average = 100 / 3 # 33.333... (float)
# ✅ Be explicit
average = 100 // 3 # 33 (floor division)
# or
average = round(100 / 3) # 33 (rounded)
Summary: Key Takeaways
Variables are references/labels, not containers
Every object has: identity (
id), type, valueMutable vs immutable determines behavior
Use
isfor identity,==for value comparisonFollow
snake_casenaming conventionScope follows LEGB rule
Beware mutable default arguments!
Type hints improve code quality
