Variables and Data Types in Python

Python FundamentalsCore SyntaxFree Lesson

Advertisement

What Is a Variable?

A variable is a named location in memory that holds a value. Think of it as a label you stick on a box — the label is the name, the box holds the data.

Memory Model:
─────────────────────────────────────────────────────────
  Variable Name       Memory Address       Value
  ─────────────      ────────────────     ──────
  name           →   0x7f3a2b10         "Alice"
  age            →   0x7f3a2b28         28
  height         →   0x7f3a2b40         1.68
  is_student     →   0x7f3a2b58         True
─────────────────────────────────────────────────────────

Python is dynamically typed — you never declare a type. Python figures it out from the value you assign.

# No type declarations needed — Python infers the type
name       = "Alice"       # str
age        = 28            # int
height     = 1.68          # float
is_student = True          # bool
nothing    = None          # NoneType

# Check the type of anything with type()
print(type(name))       # <class 'str'>
print(type(age))        # <class 'int'>
print(type(height))     # <class 'float'>
print(type(is_student)) # <class 'bool'>
print(type(nothing))    # <class 'NoneType'>

Python's 5 Core Primitive Types

Type 1: int — Integers

Whole numbers with no size limit (Python handles big integers natively).

# Basic integers
score   = 100
year    = 2024
temp    = -15
big_num = 1_000_000_000  # underscores for readability

# Python's integers are arbitrarily large — no overflow!
factorial_20 = 2432902008176640000
print(factorial_20)  # works perfectly

# Binary, octal, hex literals
binary  = 0b1010    # 10  in decimal
octal   = 0o17      # 15  in decimal
hexadec = 0xFF      # 255 in decimal

print(f"Binary 0b1010 = {binary}")
print(f"Octal  0o17   = {octal}")
print(f"Hex    0xFF   = {hexadec}")

# Integer operations
a, b = 17, 5
print(a + b)   # 22   addition
print(a - b)   # 12   subtraction
print(a * b)   # 85   multiplication
print(a / b)   # 3.4  TRUE division → always float
print(a // b)  # 3    FLOOR division → rounds down
print(a % b)   # 2    MODULO (remainder)
print(a ** b)  # 1419857  exponentiation (17^5)

Type 2: float — Floating Point Numbers

Decimal numbers — stored using IEEE 754 double precision (64-bit).

pi      = 3.14159265358979
e       = 2.718281828
gravity = -9.81
tiny    = 1.5e-10    # scientific notation: 1.5 × 10⁻¹⁰
huge    = 6.022e23   # Avogadro's number

# The famous floating point surprise
print(0.1 + 0.2)          # 0.30000000000000004  ← NOT 0.3!
print(0.1 + 0.2 == 0.3)   # False  ← dangerous!

# Correct way to compare floats
import math
print(math.isclose(0.1 + 0.2, 0.3))       # True ✅
print(abs(0.1 + 0.2 - 0.3) < 1e-9)        # True ✅

# Why does this happen?
# Floats are stored in binary. 0.1 in binary is:
# 0.0001100110011001100... (repeating) — can't be exact!

# Special float values
print(float('inf'))   # infinity
print(float('-inf'))  # negative infinity
print(float('nan'))   # not a number (result of invalid math)

# Useful float operations
import math
print(math.floor(3.7))   # 3  (round down)
print(math.ceil(3.2))    # 4  (round up)
print(round(3.14159, 2)) # 3.14 (round to 2 decimal places)
print(abs(-7.5))         # 7.5 (absolute value)

Type 3: str — Strings

Text data — an immutable sequence of Unicode characters.

# Four ways to create strings
single   = 'Hello, World!'
double   = "Hello, World!"
triple_s = '''Line one
Line two
Line three'''
triple_d = """Also
multi-line"""

# Strings are sequences — you can index and slice them
name = "Python"
#        P  y  t  h  o  n
# index  0  1  2  3  4  5
#       -6 -5 -4 -3 -2 -1   (negative indexes from end)

print(name[0])    # P      (first character)
print(name[-1])   # n      (last character)
print(name[2:5])  # tho    (slice: index 2 up to but not including 5)
print(name[:3])   # Pyt    (from start to index 3)
print(name[3:])   # hon    (from index 3 to end)
print(name[::-1]) # nohtyP (reverse!)

# Essential string methods
text = "  Hello, World!  "
print(text.strip())          # "Hello, World!"    (remove whitespace)
print(text.lower())          # "  hello, world!  "
print(text.upper())          # "  HELLO, WORLD!  "
print(text.replace("World", "Python"))  # "  Hello, Python!  "
print("Hello".startswith("He"))         # True
print("World".endswith("ld"))           # True
print("Python".find("th"))             # 2  (index of substring)
print(",".join(["a", "b", "c"]))       # "a,b,c"
print("a,b,c".split(","))              # ['a', 'b', 'c']
print("hello".capitalize())            # "Hello"
print("hello world".title())           # "Hello World"

# String formatting — 3 ways (f-strings are best!)
name, age = "Alice", 28

# Method 1: f-strings (Python 3.6+ — USE THIS)
print(f"Name: {name}, Age: {age}")
print(f"Next year: {age + 1}")          # expressions inside {}
print(f"Pi = {3.14159:.2f}")            # format spec: 2 decimal places
print(f"{'Left':<10}|{'Right':>10}")    # alignment

# Method 2: .format()
print("Name: {}, Age: {}".format(name, age))
print("Name: {n}, Age: {a}".format(n=name, a=age))

# Method 3: % operator (old style, avoid)
print("Name: %s, Age: %d" % (name, age))

# Strings are IMMUTABLE — this fails:
word = "Hello"
# word[0] = "J"  # ← TypeError! Can't change a string in place
word = "J" + word[1:]  # Must create a new string: "Jello"

Type 4: bool — Booleans

Only two values: True or False. Subclass of int in Python!

# Basic booleans
is_active  = True
is_deleted = False

# bool IS a subclass of int
print(int(True))   # 1
print(int(False))  # 0
print(True + True) # 2  (!!)

# Boolean operators
print(True and True)   # True
print(True and False)  # False
print(False or True)   # True
print(not True)        # False

# Comparison operators → produce booleans
x = 10
print(x > 5)    # True
print(x == 10)  # True   (equality)
print(x != 5)   # True   (not equal)
print(x >= 10)  # True
print(x <= 9)   # False

# Chained comparisons (Pythonic!)
age = 25
print(18 <= age < 65)     # True  (legal working age)
print(0 < x < 100)        # True

# Truthiness — almost everything is truthy in Python
# FALSY values (evaluate to False in boolean context):
falsy_values = [0, 0.0, "", [], {}, set(), None, False]
for v in falsy_values:
    print(f"bool({v!r:10}) = {bool(v)}")

# Output:
# bool(0)          = False
# bool(0.0)        = False
# bool('')         = False
# bool([])         = False
# bool({})         = False
# bool(set())      = False
# bool(None)       = False
# bool(False)      = False

Type 5: None — The Absence of Value

None is Python's null value — a singleton object of type NoneType.

# None represents "no value" or "not yet set"
result = None
user   = None

# Check for None with `is` (not ==)
print(result is None)   # True   ← correct
print(result == None)   # True   ← works but avoid this style

# Common use: function with optional return
def find_user(user_id):
    users = {1: "Alice", 2: "Bob"}
    return users.get(user_id)    # returns None if not found

user = find_user(99)
if user is None:
    print("User not found")
else:
    print(f"Found: {user}")

# None in function arguments (default value pattern)
def greet(name=None):
    if name is None:
        name = "World"
    print(f"Hello, {name}!")

greet()          # Hello, World!
greet("Alice")   # Hello, Alice!

Type Conversion (Casting)

# ─── Explicit Conversion ──────────────────────────────────────────
# str → int
num = int("42")           # 42
num = int("  42  ")       # 42 (strips whitespace)
# int("3.14")             # ValueError! Can't convert decimal string
num = int(float("3.14"))  # 3  (go via float first)

# str → float
f = float("3.14")         # 3.14
f = float("1e5")          # 100000.0

# int → str
s = str(42)               # "42"
s = str(3.14)             # "3.14"

# int/float → bool
print(bool(0))            # False
print(bool(42))           # True
print(bool(0.0))          # False
print(bool(3.14))         # True

# bool → int
print(int(True))          # 1
print(int(False))         # 0

# ─── Common Conversion Pitfalls ───────────────────────────────────
# Pitfall 1: int("3.14") fails — must go through float
try:
    x = int("3.14")
except ValueError as e:
    print(f"Error: {e}")          # invalid literal for int()
x = int(float("3.14"))            # Correct: 3

# Pitfall 2: Floor division with floats
print(10 / 3)    # 3.3333...  (true division)
print(10 // 3)   # 3          (floor division — integer result even for floats)
print(10.0 // 3) # 3.0        (floor division returns float if input is float)

# Pitfall 3: String number comparisons
print("10" > "9")   # False! (lexicographic: "1" < "9")
print(10  > 9)      # True   (numeric comparison)
# Always convert strings to numbers before numeric comparison

Variable Naming Rules & Conventions

# ✅ VALID names
my_variable = 1
_private     = 2
__dunder__   = 3
CamelCase    = 4
name123      = 5

# ❌ INVALID names (SyntaxError)
# 1variable = 1    ← can't start with digit
# my-var    = 2    ← hyphen is subtraction operator
# class     = 3    ← 'class' is a reserved keyword

# Python reserved keywords (cannot use as variable names):
import keyword
print(keyword.kwlist)
# ['False', 'None', 'True', 'and', 'as', 'assert', 'async', 'await',
#  'break', 'class', 'continue', 'def', 'del', 'elif', 'else', ...]

# ─── PEP 8 Naming Conventions ────────────────────────────────────
user_name    = "alice"      # snake_case for variables and functions
MAX_RETRIES  = 3            # UPPER_CASE for constants
class UserProfile:          # PascalCase for class names
    pass
_internal    = True         # leading underscore = "private by convention"
__very_priv  = True         # double underscore = name mangling (advanced)

# Multiple assignment
x = y = z = 0              # all three equal 0
a, b, c = 1, 2, 3          # tuple unpacking
first, *rest = [1,2,3,4,5] # star unpacking: first=1, rest=[2,3,4,5]

# Swap variables (Pythonic — no temp variable needed)
x, y = 10, 20
x, y = y, x
print(x, y)  # 20 10

Memory Model: How Python Actually Stores Variables

# In Python, variables are REFERENCES (pointers) to objects in memory
# Not boxes containing values — labels pointing to objects!

a = 42
b = a       # b points to the SAME object as a

print(id(a) == id(b))  # True — same memory address!

# Immutable types (int, float, str, bool, None):
# Changing a variable makes it point to a NEW object
a = 100     # a now points to a different object
print(id(a) == id(b))  # False — a was rebound

# Python caches small integers (-5 to 256) for performance
x = 256
y = 256
print(x is y)   # True  (same cached object)

x = 257
y = 257
print(x is y)   # False (not cached — different objects)
# ↑ This is why you should NEVER use `is` to compare values!
# Always use == for value equality, `is` only for None/True/False

# Check memory usage
import sys
print(sys.getsizeof(1))        # 28 bytes (int)
print(sys.getsizeof(1.0))      # 24 bytes (float)
print(sys.getsizeof(""))       # 49 bytes (empty string)
print(sys.getsizeof("hello"))  # 54 bytes (5 chars + overhead)

Real-World Exercise: Temperature Converter

Build a complete temperature conversion utility using what you've learned:

def convert_temperature(value, from_unit, to_unit):
    """
    Convert temperature between Celsius, Fahrenheit, and Kelvin.

    Args:
        value (float): Temperature value to convert
        from_unit (str): Source unit — 'C', 'F', or 'K'
        to_unit (str): Target unit — 'C', 'F', or 'K'

    Returns:
        float: Converted temperature, rounded to 2 decimal places

    Raises:
        ValueError: If invalid unit or below absolute zero
    """
    from_unit = from_unit.upper()
    to_unit   = to_unit.upper()

    valid_units = {'C', 'F', 'K'}
    if from_unit not in valid_units or to_unit not in valid_units:
        raise ValueError(f"Unit must be one of {valid_units}")

    # Step 1: Convert to Celsius (common intermediate)
    if from_unit == 'C':
        celsius = float(value)
    elif from_unit == 'F':
        celsius = (float(value) - 32) * 5 / 9
    elif from_unit == 'K':
        celsius = float(value) - 273.15

    # Validate: nothing below absolute zero
    if celsius < -273.15:
        raise ValueError(f"Temperature {celsius}°C is below absolute zero!")

    # Step 2: Convert from Celsius to target
    if to_unit == 'C':
        result = celsius
    elif to_unit == 'F':
        result = celsius * 9 / 5 + 32
    elif to_unit == 'K':
        result = celsius + 273.15

    return round(result, 2)


# ─── Test all conversions ──────────────────────────────────────────
test_cases = [
    (100,    'C', 'F', 212.0),       # Boiling point of water
    (32,     'F', 'C', 0.0),         # Freezing point
    (0,      'C', 'K', 273.15),      # Absolute scale
    (98.6,   'F', 'C', 37.0),        # Body temperature
    (300,    'K', 'C', 26.85),       # Room temperature in Kelvin
    (-40,    'C', 'F', -40.0),       # The one point where C == F!
]

print(f"{'From':>8} {'To':>6} {'Input':>8} {'Result':>8} {'Expected':>10} {'Pass':>6}")
print("-" * 52)
for value, from_u, to_u, expected in test_cases:
    result = convert_temperature(value, from_u, to_u)
    passed = "✅" if abs(result - expected) < 0.01 else "❌"
    print(f"{from_u:>8} {to_u:>6} {value:>8} {result:>8} {expected:>10} {passed:>6}")

Output:

    From     To    Input   Result   Expected   Pass
----------------------------------------------------
       C      F      100    212.0      212.0      ✅
       F      C       32      0.0        0.0      ✅
       C      K        0   273.15     273.15      ✅
       F      C     98.6     37.0       37.0      ✅
       K      C      300    26.85      26.85      ✅
       C      F      -40    -40.0      -40.0      ✅

Common Mistakes and How to Avoid Them

# ❌ Mistake 1: Integer division returning float when you want int
result = 10 / 2       # 5.0  (float — might cause issues)
result = 10 // 2      # 5    ✅ (int — if you want integer division)

# ❌ Mistake 2: Comparing floats with ==
print(0.1 + 0.2 == 0.3)         # False — floating point error!
import math
print(math.isclose(0.1 + 0.2, 0.3, rel_tol=1e-9))  # True ✅

# ❌ Mistake 3: Using = instead of ==
x = 5
if x == 5:     # comparison ✅
    print("five")
# if x = 5:   # assignment — SyntaxError ✅ (Python catches this)

# ❌ Mistake 4: Checking None with ==
value = None
if value == None:   # works but bad style
    pass
if value is None:   # ✅ Pythonic — use `is` for None
    pass

# ❌ Mistake 5: Mutable default argument (advanced gotcha)
def append_to(item, lst=[]):  # ← lst is shared across ALL calls!
    lst.append(item)
    return lst

print(append_to(1))   # [1]
print(append_to(2))   # [1, 2]  ← unexpected! Not [2]
print(append_to(3))   # [1, 2, 3]  ← the list persists between calls!

# Fix: use None as default, create fresh list inside
def append_to_fixed(item, lst=None):
    if lst is None:
        lst = []
    lst.append(item)
    return lst

Quick Reference Card

TypeLiteralMutable?Example
int42, -7, 0b101Noage = 28
float3.14, 1e-10Noprice = 9.99
str"hi", 'hi', """hi"""Noname = "Alice"
boolTrue, FalseNoactive = True
NoneTypeNoneNoresult = None

Key Takeaways

  1. Python is dynamically typed — no type declarations; Python infers type from the value
  2. type(x) reveals any variable's type at runtime
  3. Integers are unlimited in Python — no overflow errors like C/Java
  4. Never compare floats with == — use math.isclose() instead
  5. f-strings (f"Hello {name}") are the modern, preferred way to format strings
  6. Use is only for None, True, and False — use == for everything else
  7. Variables are references, not boxes — they point to objects in memory
  8. Truthiness rules: 0, "", [], {}, None, False are all falsy

Advertisement

Need Expert Python Help?

Get personalized tutoring, project support, or professional consulting.

Advertisement