Python Variables and Data Types
Learning Objectives
By the end of this tutorial, you will be able to:
- Understand how Python variables differ from variables in statically-typed languages like C and Java
- Identify and use all major Python data types including numeric, text, boolean, sequence, mapping, set, and binary types
- Perform type checking with
type()andisinstance(), and convert between types safely - Apply dynamic typing principles to write flexible, idiomatic Python code
- Recognize and avoid common mistakes related to variable assignment and type behavior
What Are Variables?
In Python, a variable is a name that refers to a value stored in memory. Unlike C or Java where variables have fixed types declared at compile time, Python variables are labels (or references) bound to objects. When you write x = 42, Python creates an integer object with the value 42 and binds the name x to it.
Python uses reference counting for memory management. Every object tracks how many names refer to it. When the reference count drops to zero, the memory is freed automatically.
| Feature | C / Java | Python |
|---|---|---|
| Type declaration | Required (int x = 42;) | Not required (x = 42) |
| Type binding | Compile-time (static) | Run-time (dynamic) |
| Memory management | Manual or GC | Reference counting + garbage collector |
| Variable meaning | Memory location | Reference to an object |
Creating Variables
Creating a variable in Python is as simple as assigning a value. No type declaration is required.
age = 25 # int
temperature = 98.6 # float
name = "Alice" # str
is_active = True # bool
x, y, z = 10, 20, 30 # Multiple assignment
a = b = c = 0 # Same value to all
m, n = 5, 10
m, n = n, m # Swap β Pythonic way
print(m, n) # 10 5
Python supports optional type annotations (Python 3.6+) for clarity and static analysis, but they do not enforce types at runtime:
count: int = 42
name: str = "Bob"
count = "now I'm a string" # Valid β type hints are not enforced
Python Data Types Deep Dive
Numeric Types
int β Integers
Python integers have unlimited precision. Unlike C's int, they can be arbitrarily large.
big_number = 10 ** 100 # No overflow
binary_num = 0b1010 # Binary: 10
octal_num = 0o17 # Octal: 15
hex_num = 0xFF # Hex: 255
population = 7_900_000_000 # Underscores for readability (3.6+)
float β Floating-Point Numbers
Python floats are IEEE 754 double-precision (64-bit).
speed_of_light = 3e8 # Scientific notation: 300000000.0
# β οΈ Precision gotcha
print(0.1 + 0.2) # 0.30000000000000004
print(0.1 + 0.2 == 0.3) # False
import math
print(math.isclose(0.1 + 0.2, 0.3)) # True β use this instead
print(math.inf) # inf (positive infinity)
print(float('nan') == float('nan')) # False β NaN != NaN
complex β Complex Numbers
z = 3 + 4j
print(z.real, z.imag) # 3.0 4.0
print(abs(z)) # 5.0 (magnitude)
decimal β Precise Decimal Arithmetic
For financial calculations where IEEE 754 floating-point errors are unacceptable:
from decimal import Decimal, getcontext, ROUND_HALF_UP
getcontext().prec = 28
price = Decimal('19.99')
tax = Decimal('0.08')
total = (price * tax).quantize(Decimal('0.01'), rounding=ROUND_HALF_UP)
print(total) # 1.60
Text Type
str β Strings
Strings in Python are immutable sequences of Unicode characters.
s = "Hello, World!"
raw = r"C:\new\folder\test" # Raw string β no escape processing
multi = """Triple quotes
span multiple lines"""
# f-strings (3.6+)
name = "Alice"
age = 30
print(f"My name is {name} and I'll be {age + 1} next year.")
# Strings are immutable
# s[0] = "h" # TypeError
s_new = "h" + s[1:] # Create a new string
# Common methods
text = " Hello, Python World! "
print(text.strip()) # "Hello, Python World!"
print(text.lower()) # " hello, python world! "
print(text.replace("World", "Universe")) # " Hello, Python Universe! "
print(text.split(",")) # [" Hello", " Python World! "]
print("-".join(["a", "b", "c"])) # a-b-c
Boolean Type
bool β Booleans
Booleans are a subclass of integers. True equals 1, False equals 0.
print(True + True) # 2
print(isinstance(True, int)) # True
# Truthy and Falsy values
# Everything evaluates to False except:
# False, None, 0, 0.0, 0j, "", [], (), {}, set(), frozenset(), range(0)
print(bool("")) # False
print(bool("False")) # True β non-empty string!
print(bool(0)) # False
print(bool(-1)) # True β any non-zero number
Sequence Types
list β Lists (Mutable, Ordered)
fruits = ["apple", "banana", "cherry"]
mixed = [1, "hello", 3.14, True, None] # Mixed types allowed
fruits.append("date")
fruits.insert(1, "blueberry")
fruits.remove("banana")
popped = fruits.pop()
# Slicing
print(fruits[0:2]) # ['apple', 'blueberry']
print(fruits[::-1]) # Reversed copy
# List comprehension
squares = [x**2 for x in range(10)]
evens = [x for x in range(20) if x % 2 == 0]
# Sorting
numbers = [3, 1, 4, 1, 5]
numbers.sort() # In-place
sorted_list = sorted(numbers, reverse=True) # New list
tuple β Tuples (Immutable, Ordered)
coordinates = (10, 20)
single = (42,) # Trailing comma required for single-item tuple
# Tuple unpacking
x, y = coordinates
print(x, y) # 10 20
# When to use tuples: fixed collections, dict keys, return values
# Named tuples for readability
from collections import namedtuple
Point = namedtuple('Point', ['x', 'y'])
p = Point(3, 4)
print(p.x, p.y) # 3 4
range β Range Objects
print(list(range(5))) # [0, 1, 2, 3, 4]
print(list(range(2, 8))) # [2, 3, 4, 5, 6, 7]
print(list(range(0, 10, 2))) # [0, 2, 4, 6, 8]
r = range(1_000_000)
print(999_999 in r) # True β O(1) membership test, no list in memory
Mapping Type
dict β Dictionaries (Mutable, Key-Value Pairs)
person = {"name": "Alice", "age": 30}
person["email"] = "a@b.com" # Add
person["age"] = 31 # Update
del person["email"] # Delete
print(person.get("phone", "N/A")) # Default if key missing
# Dict comprehension
squares = {x: x**2 for x in range(6)}
# Merging (3.9+)
defaults = {"color": "blue", "size": "medium"}
custom = {"color": "red"}
merged = defaults | custom # {'color': 'red', 'size': 'medium'}
# Iteration
for key, value in person.items():
print(f"{key}: {value}")
Set Types
set and frozenset
colors = {"red", "green", "blue"} # Duplicates removed automatically
empty_set = set() # Not {} β that creates an empty dict!
colors.add("yellow")
colors.discard("red") # No error if missing
a = {1, 2, 3, 4}
b = {3, 4, 5, 6}
print(a | b) # Union: {1, 2, 3, 4, 5, 6}
print(a & b) # Intersection: {3, 4}
print(a - b) # Difference: {1, 2}
print(a ^ b) # Sym diff: {1, 2, 5, 6}
fs = frozenset([1, 2, 3]) # Immutable set β can be dict key or set element
Binary Types
bytes, bytearray, memoryview
# bytes β immutable
b = b"Hello"
print(b[0]) # 72 (ASCII for 'H')
# bytearray β mutable
ba = bytearray(b"Hello")
ba[0] = 74 # Change to 'J'
print(ba) # b'Jello'
# Encoding/decoding
text = "Hello, δΈη"
encoded = text.encode("utf-8")
decoded = encoded.decode("utf-8")
print(decoded) # Hello, δΈη
# memoryview β zero-copy access to binary data
data = bytearray(b"Hello, World!")
mv = memoryview(data)
mv[0] = 74
print(data) # bytearray(b'Jello, World!')
Type Checking and Conversion
Checking Types
x = 42
print(type(x)) # <class 'int'>
print(isinstance(x, int)) # True β preferred for most cases
# isinstance() recognizes subclasses; type() does not
class MyInt(int): pass
num = MyInt(42)
print(type(num) == int) # False
print(isinstance(num, int)) # True
Type Conversion Rules
| Function | Example | Result | Notes |
|---|---|---|---|
int("42") | "42" β 42 | int | int(3.9) β 3 (truncates, not rounds) |
float("3.14") | "3.14" β 3.14 | float | float(5) β 5.0 |
str(42) | 42 β "42" | str | str(True) β "True" |
bool("") | "" β False | bool | See truthy/falsy section |
list("abc") | "abc" β ["a","b","c"] | list | |
set([1,1,2]) | [1,1,2] β {1, 2} | set | Duplicates removed |
Common Gotchas
print(bool("False")) # True β non-empty string!
print(bool("0")) # True β non-empty string!
print(int("3.14")) # ValueError β use int(float("3.14")) β 3
# Safe conversion pattern
def safe_int(value, default=0):
try:
return int(value)
except (ValueError, TypeError):
return default
print(safe_int("hello")) # 0
print(safe_int("42")) # 42
Dynamic Typing Explained
Variables don't have fixed types β the objects they reference do. Reassignment changes which object the name refers to.
x = 42
x = "Hello" # Now refers to a string
x = [1, 2, 3] # Now refers to a list
# is vs ==
a = [1, 2, 3]
b = [1, 2, 3]
c = a
print(a == b) # True β same value
print(a is b) # False β different objects
print(a is c) # True β same object
# Small integer caching: Python caches -5 to 256
x = 256; y = 256
print(x is y) # True β same cached object
x = 257; y = 257
print(x is y) # False (usually) β different objects
Variable Naming Rules and Best Practices
| Element | Convention | Example |
|---|---|---|
| Variables | snake_case | user_name, total_count |
| Functions | snake_case | get_user(), calculate_total() |
| Classes | PascalCase | UserProfile, DataProcessor |
| Constants | UPPER_SNAKE_CASE | MAX_RETRIES, PI |
| Private | Leading underscore | _internal_var |
# Valid names
_name = "valid"
user_name = "valid"
_count2 = "valid"
# Invalid names
# 2name = "invalid" # Starts with number
# my-var = "invalid" # Hyphen
# class = "invalid" # Reserved keyword
import keyword
print(keyword.kwlist) # All reserved keywords
Memory and Variables
id() β Object Identity
a = [1, 2, 3]
b = [1, 2, 3]
c = a
print(id(a) == id(b)) # False β different objects
print(a is b) # False
print(a is c) # True β same object
# Mutability affects references
c.append(4)
print(a) # [1, 2, 3, 4] β both a and c refer to the same list
Reference Counting
import sys
a = [1, 2, 3]
print(sys.getrefcount(a)) # 2 (variable + function call argument)
b = a
print(sys.getrefcount(a)) # 3
del b
print(sys.getrefcount(a)) # 2
Common Mistakes
1. Mutable Default Arguments
# β WRONG β default list shared across calls
def append_to(item, target=[]):
target.append(item)
return target
print(append_to(1)) # [1]
print(append_to(2)) # [1, 2] β unexpected!
# β
CORRECT
def append_to_fixed(item, target=None):
if target is None:
target = []
target.append(item)
return target
2. Floating-Point Comparison
# β WRONG
if 0.1 + 0.2 == 0.3: print("Equal")
# β
CORRECT
import math
if math.isclose(0.1 + 0.2, 0.3): print("Equal")
3. Modifying a List While Iterating
# β WRONG β skips elements
numbers = [1, 2, 3, 4, 5]
for n in numbers:
if n % 2 == 0:
numbers.remove(n)
# β
CORRECT β list comprehension
numbers = [n for n in numbers if n % 2 != 0]
4. Confusing = with ==
x = 5 # Assignment
if x == 5: # Comparison β two equals signs
print("x is 5")
Practice Exercises
Exercise 1: Type Explorer
Write a function that accepts a string and converts it to the most appropriate type.
Exercise: Create identify_and_convert(value) that returns True/False for "True"/"False", an int for integer strings, a float for float strings, and the original string otherwise.
See Solution
def identify_and_convert(value):
if value.lower() == "true":
return True
if value.lower() == "false":
return False
try:
return int(value)
except ValueError:
pass
try:
return float(value)
except ValueError:
pass
return value
print(identify_and_convert("True")) # True
print(identify_and_convert("42")) # 42
print(identify_and_convert("3.14")) # 3.14
print(identify_and_convert("hello")) # "hello"
Exercise 2: Variable Swap and Conversion
Exercise: Given a = 10, b = "20", c = 30.0: swap a and c, convert b to int, build a list of all three, and print each element's type.
See Solution
a = 10
b = "20"
c = 30.0
a, c = c, a # Swap
b = int(b) # Convert
my_list = [a, b, c]
for i, item in enumerate(my_list):
print(f"Element {i}: value={item}, type={type(item).__name__}")
# Element 0: value=30.0, type=float
# Element 1: value=20, type=int
# Element 2: value=10, type=int
Exercise 3: Conversion Chain
Exercise: Write conversion_chain(value) that converts any value to string, list, bool, and numeric (if possible), printing each step.
See Solution
def conversion_chain(value):
print(f"Original: {value!r} ({type(value).__name__})")
print(f" String: {str(value)!r} (len {len(str(value))})")
print(f" List: {list(str(value))}")
print(f" Bool: {bool(value)}")
try:
print(f" Int: {int(value)}")
except (ValueError, TypeError):
try:
print(f" Float: {float(value)}")
except (ValueError, TypeError):
print(" Numeric: not possible")
print()
conversion_chain(42)
conversion_chain("Hello")
conversion_chain(None)
Key Takeaways
- Python variables are names, not containers. They reference objects in memory. The object has a type; the variable does not.
- Dynamic typing makes Python flexible but requires discipline. Always ensure variables hold the expected type in larger programs.
- Use
isinstance()overtype()for type checking when you want to support inheritance hierarchies. - Be mindful of mutability. Lists, dicts, and sets are shared by reference. Use
.copy()or slicing for independent copies. - Prefer
math.isclose()for floating-point comparison instead of==. IEEE 754 arithmetic introduces precision errors. - Follow PEP 8 naming conventions.
snake_casefor variables/functions,PascalCasefor classes,UPPER_SNAKE_CASEfor constants.