Python Variables, Data Types, and Type Conversion: How Python Stores Data in Memory, Namespaces, Reference Counting, Type System, and Every Data Type Explained

Python Variables, Data Types, and Type Conversion: How Python Stores Data in Memory, Namespaces, Reference Counting, Type System, and Every Data Type Explained

Most Python tutorials start with x = 5 and say “x is a variable that stores the value 5.” That is technically wrong. Python variables do NOT store values. They are labels — name tags attached to objects that live in memory. Understanding this distinction is the foundation of everything in Python: why lists behave differently from integers, why is and == give different answers, and why function arguments sometimes change the original data and sometimes do not.

This post goes deeper than any tutorial you have seen. We will start with how your computer actually stores data (RAM, addresses, the heap), then build up to Python’s variable model (namespaces, references, reference counting), and finally cover every data type with real-life analogies and type conversion. By the end, you will understand Python’s memory model — not just the syntax.

Table of Contents

  • How Data Lives in Your Computer
  • RAM, Addresses, and the Three Properties
  • What Happens When You Write x = 42
  • Variables Are Labels, Not Boxes
  • The Box Analogy Is Wrong
  • The Label Analogy Is Right
  • Proving It with id()
  • Namespaces: Where Python Stores Variable Names
  • What a Namespace Actually Stores
  • Global vs Local Namespaces
  • Local Variable Tables in Functions
  • Reference Counting and Garbage Collection
  • What del Actually Does
  • Python Data Types Overview
  • Everything Is an Object
  • Mutable vs Immutable Types
  • Numeric Types
  • int (Integers)
  • float (Floating Point)
  • complex (Complex Numbers)
  • bool (Boolean)
  • Text Type
  • str (Strings)
  • Sequence Types
  • list (Mutable Sequences)
  • tuple (Immutable Sequences)
  • range (Numeric Sequences)
  • Mapping Type
  • dict (Dictionaries)
  • Set Types
  • set (Mutable Unique Collection)
  • frozenset (Immutable Set)
  • None Type
  • Type Checking
  • type() vs isinstance()
  • Type Conversion (Casting)
  • Implicit Conversion (Coercion)
  • Explicit Conversion
  • Conversion Gotchas
  • Integer Interning and String Interning
  • Common Mistakes
  • Interview Questions
  • Wrapping Up

How Data Lives in Your Computer

RAM, Addresses, and the Three Properties

Before understanding Python variables, you need to understand where data actually lives. When your Python program runs, it becomes a process — the operating system allocates a chunk of RAM (Random Access Memory) for it. Every piece of data your program uses lives somewhere in this RAM.

Your computer's memory (RAM) is like a massive hotel:
  - Each room has a ROOM NUMBER (memory address)
  - Each room has CONTENTS (the actual value)
  - Each room has a TYPE tag on the door (what kind of data is inside)

  ┌──────────────┬────────────────┬─────────────┐
  │ Address      │ Value          │ Type        │
  ├──────────────┼────────────────┼─────────────┤
  │ 0x7F8B001    │ 42             │ int         │
  │ 0x7F8B002    │ 3.14           │ float       │
  │ 0x7F8B003    │ "hello"        │ str         │
  │ 0x7F8B004    │ [1, 2, 3]      │ list        │
  └──────────────┴────────────────┴─────────────┘

Every piece of data in memory has THREE properties:
  1. ADDRESS — where it lives (the room number)
  2. VALUE   — what it contains (the contents)
  3. TYPE    — what kind of data it is (the door tag)

What Happens When You Write x = 42

Step 1: Python creates an integer OBJECT on the heap (a region of RAM)
        This object is a C struct containing:
          - Reference count: 1
          - Type pointer: points to the int class
          - Value: 42

Step 2: Python adds the name "x" to the current NAMESPACE
        The namespace is a dictionary: {"x": address_of_int_object}

Step 3: The name "x" now POINTS TO (references) the integer object

        NAMESPACE (dict)              HEAP (RAM)
        ┌──────────────┐            ┌─────────────────────┐
        │ "x" ─────────┼───────────▶│ int object          │
        └──────────────┘            │   refcount: 1       │
                                    │   type: class 'int' │
                                    │   value: 42         │
                                    │   addr: 0x7F8B...   │
                                    └─────────────────────┘

The variable "x" does NOT contain 42.
The variable "x" contains the ADDRESS of an object that contains 42.
x = 42
print(id(x))    # e.g., 140234866221360 — the memory address of the int object
print(type(x))  # <class 'int'> — the type of the object x points to
print(x)        # 42 — the value of the object x points to

Variables Are Labels, Not Boxes

The Box Analogy Is Wrong

WRONG mental model (the "box" analogy):
  x = 42      →  x is a box containing 42
  y = x       →  y is a new box, copy of 42 put inside
  y = 99      →  y box now contains 99, x box still has 42

  This model BREAKS with lists:
    a = [1, 2, 3]
    b = a
    b.append(4)
    print(a)    # [1, 2, 3, 4] ← Wait, I only changed b!

The Label Analogy Is Right

CORRECT mental model (the "label" analogy):
  a = [1, 2, 3]  → Create list object, stick label "a" on it
  b = a           → Stick label "b" on the SAME list object
  b.append(4)     → Modify the list object (both labels still point to it)
  print(a)        → [1, 2, 3, 4] — correct! Both labels, same object

Proving It with id()

a = [1, 2, 3]
b = a
print(id(a))  # 140234866221360
print(id(b))  # 140234866221360 ← SAME address! Same object!
print(a is b) # True

b.append(4)
print(a)      # [1, 2, 3, 4] — both labels, same list

b = [10, 20, 30]  # b now points to a DIFFERENT object
print(a is b)      # False — different objects now

Namespaces: Where Python Stores Variable Names

What a Namespace Actually Stores

x = 42
name = "Naveen"
# The namespace stores name → address (not name → value)
# Python displays values for convenience:
print(globals())  # {..., 'x': 42, 'name': 'Naveen'}
# But internally: {'x': 0x7F8B001, 'name': 0x7F8B002}

Global vs Local Namespaces

x = 10  # Global namespace

def my_function():
    y = 20  # Local namespace (exists only during function execution)
    print(x)   # Looks in local first, then global → finds x=10

my_function()
# print(y)   # NameError! y does not exist in the global namespace

# LEGB rule: Local → Enclosing → Global → Built-in

Local Variable Tables in Functions

# Inside functions, CPython uses a fixed-size C array (not a dict)
# This makes function variable access FASTER than global access
def my_function(a, b):
    total = a + b
    return total

print(my_function.__code__.co_varnames)  # ('a', 'b', 'total')
# Python knew all three names at compile time and assigned them array positions
# Accessing total = array[2] — direct index lookup, no hashing

Reference Counting and Garbage Collection

import sys

x = [1, 2, 3]
print(sys.getrefcount(x))  # 2 (x + getrefcount temporary reference)

y = x
print(sys.getrefcount(x))  # 3

del y
print(sys.getrefcount(x))  # 2

del x  # refcount drops to 0 → Python immediately frees the memory

What del Actually Does

# del removes the NAME from the namespace, NOT the object
x = [1, 2, 3]
y = x
del x
print(y)  # [1, 2, 3] — the list is fine, y still points to it

Python Data Types Overview

Everything Is an Object

x = 42
print(type(x))        # <class 'int'>
print(x.bit_length()) # 6 — even integers have methods!

Mutable vs Immutable Types

Category Immutable (cannot change) Mutable (can change in place)
Numericint, float, complex, bool
Textstr
Sequencetuple, range, frozensetlist
Mappingdict
Setfrozensetset
NoneNoneType

# IMMUTABLE: changing value creates a NEW object
x = 42
print(id(x))    # address A
x = 43
print(id(x))    # address B (different object!)

# MUTABLE: changing value modifies the SAME object
scores = [90, 85, 92]
print(id(scores))    # address A
scores.append(88)
print(id(scores))    # address A (same object!)

Numeric Types

int (Integers)

age = 25
population = 1_400_000_000    # Underscores for readability
big = 10 ** 100               # Python ints have UNLIMITED precision

print(10 / 3)     # 3.3333 (true division → always returns float!)
print(10 // 3)    # 3 (floor division → returns int)
print(10 % 3)     # 1 (modulus)
print(10 ** 3)    # 1000 (exponentiation)

float (Floating Point)

pi = 3.14159
print(0.1 + 0.2)           # 0.30000000000000004 (NOT 0.3!)
print(0.1 + 0.2 == 0.3)    # False!

# Fix with Decimal for exact arithmetic
from decimal import Decimal
print(Decimal('0.1') + Decimal('0.2') == Decimal('0.3'))  # True

complex (Complex Numbers)

z = 3 + 4j
print(z.real)    # 3.0
print(z.imag)    # 4.0
print(abs(z))    # 5.0 (magnitude)

bool (Boolean)

is_active = True
print(isinstance(True, int))   # True! (bool is a subclass of int)
print(True + True)             # 2

# Falsy: False, 0, 0.0, "", [], {}, (), set(), None
# Truthy: everything else
print(bool(0))         # False
print(bool("hello"))   # True
print(bool([]))        # False

Text Type

str (Strings)

name = "Naveen"
# Strings are IMMUTABLE
upper_name = name.upper()  # Returns NEW string, name unchanged

# f-strings (Python 3.6+)
age = 30
print(f"My name is {name} and I am {age} years old")
print(f"Pi is {3.14159:.2f}")  # "Pi is 3.14"
print(f"{1000000:,}")          # "1,000,000"

# Common methods
print("  hello  ".strip())         # "hello"
print("hello world".split())      # ['hello', 'world']
print(", ".join(["a", "b", "c"])) # "a, b, c"
print("42".isdigit())             # True

Sequence Types

list (Mutable Sequences)

fruits = ["apple", "banana", "cherry"]
fruits.append("date")
fruits.insert(1, "blueberry")
fruits.remove("banana")
fruits.sort()

# copy vs reference
a = [1, 2, 3]
b = a          # Same object (reference)
c = a.copy()   # New object (shallow copy)
a.append(4)
print(b)  # [1, 2, 3, 4] — same object as a
print(c)  # [1, 2, 3] — separate copy

tuple (Immutable Sequences)

coordinates = (10.5, 20.3)
single = (42,)        # Single-element tuple NEEDS the comma!
not_tuple = (42)       # Just 42 in parentheses, NOT a tuple

# Tuple unpacking
x, y = coordinates     # x=10.5, y=20.3
a, b = 1, 2
a, b = b, a            # Swap — no temp variable needed!

range (Numeric Sequences)

# range is memory efficient — does NOT store values, generates on demand
r = range(1_000_000_000)   # Uses almost NO memory!
print(list(range(5)))       # [0, 1, 2, 3, 4]

Mapping Type

dict (Dictionaries)

student = {"name": "Naveen", "age": 30, "scores": [90, 85]}
print(student["name"])            # "Naveen"
print(student.get("email", "N/A"))  # "N/A" (no KeyError)

student["email"] = "n@email.com"  # Add key
del student["scores"]              # Remove key

for key, value in student.items():
    print(f"{key}: {value}")

# Keys must be immutable (hashable): str, int, tuple — NOT list

Set Types

set (Mutable Unique Collection)

a = {1, 2, 3, 4}
b = {3, 4, 5, 6}
print(a | b)   # {1, 2, 3, 4, 5, 6} — union
print(a & b)   # {3, 4} — intersection
print(a - b)   # {1, 2} — difference

# Deduplication
emails = ["a@x.com", "b@x.com", "a@x.com"]
unique = list(set(emails))  # ['a@x.com', 'b@x.com']

frozenset (Immutable Set)

fs = frozenset([1, 2, 3])
# fs.add(4)  # AttributeError — cannot modify
# frozenset CAN be a dict key or set member (set cannot)

None Type

result = None
a = None
b = None
print(a is b)  # True — there is only ONE None object (singleton)

# Always use "is" for None comparison, not "=="
if result is None:
    print("No result")

Type Checking

type() vs isinstance()

print(type(42))             # <class 'int'>
print(type(True))           # <class 'bool'>
print(isinstance(True, int))  # True (bool IS a subclass of int)
print(isinstance(42, (int, float)))  # True (check multiple types)

# PREFER isinstance() — handles subclasses correctly

Type Conversion (Casting)

Implicit Conversion (Coercion)

# int + float → float (narrower type promoted)
print(10 + 3.14)       # 13.14 (float)
print(True + 2)        # 3 (bool → int)
# "age: " + 25         # TypeError! No implicit string conversion

Explicit Conversion

print(int(3.9))         # 3 (truncates, does NOT round!)
print(int("42"))        # 42
print(float("3.14"))    # 3.14
print(str(42))          # "42"
print(bool(""))         # False
print(bool("hello"))    # True
print(list("hello"))    # ['h', 'e', 'l', 'l', 'o']
print(set([1, 2, 2]))   # {1, 2}

Conversion Gotchas

# int() truncates, does NOT round
print(int(3.9))         # 3 (not 4!) — use round(3.9) for rounding

# bool("False") is True!
print(bool("False"))    # True — non-empty string is truthy!
print(bool(""))         # False — only EMPTY string is falsy

# int("3.14") → ValueError! Convert to float first
print(int(float("3.14")))  # 3

Integer Interning and String Interning

# Python pre-creates integers from -5 to 256
a = 42
b = 42
print(a is b)  # True — SAME object (interned)

a = 1000
b = 1000
print(a is b)  # May be False — different objects (not interned)

# LESSON: Never use "is" to compare VALUES. Use "=="
# "is" only reliable for None, True, False

Common Mistakes

  1. Thinking variables store values — variables are labels (references) pointing to objects. Two variables can point to the same object.
  2. Using is instead of ==is checks identity (same object). == checks equality (same value). Use == for values.
  3. Expecting int(3.9) to round — it truncates. Use round(3.9) for rounding.
  4. Thinking bool("False") is False — any non-empty string is truthy.
  5. Mutating a default list argumentdef f(items=[]) shares the SAME list across calls. Use def f(items=None).
  6. Comparing floats with ==0.1 + 0.2 == 0.3 is False. Use round() or Decimal.
  7. Forgetting strings are immutablename.upper() returns a NEW string, does not modify name.

Interview Questions

Q: What is the difference between is and == in Python? A: == checks value equality. is checks identity (same object in memory, same id()). a = [1, 2]; b = [1, 2]a == b is True but a is b is False. Use is only for None.

Q: What are mutable and immutable types? A: Immutable objects cannot change after creation (int, float, str, tuple, frozenset). Mutable objects can change in place (list, dict, set). Two variables pointing to the same mutable object share changes.

Q: What is a namespace in Python? A: A dictionary mapping variable names to object references. Python has local, enclosing, global, and built-in namespaces. Lookup follows LEGB rule.

Q: What does del x do? A: Removes the name x from the namespace and decreases the object’s reference count. The object is only destroyed when its reference count reaches zero.

Q: Why is 0.1 + 0.2 != 0.3? A: Floats use binary representation (IEEE 754). 0.1 cannot be represented exactly in binary, causing tiny errors. Use Decimal for exact arithmetic or math.isclose() for comparisons.

Q: What is integer interning? A: Python pre-creates integers -5 to 256 at startup and reuses them. a = 42; b = 42; a is b → True (same cached object). Larger integers may create separate objects.

Q: What is the difference between shallow copy and deep copy? A: Shallow copy (list.copy()) creates a new list but elements reference the same objects. Deep copy (copy.deepcopy()) recursively copies all nested objects. Use deep copy for lists containing mutable objects.

Wrapping Up

Variables in Python are not boxes that store values — they are labels attached to objects in memory. This single mental model shift explains why lists behave differently from integers, why is and == give different answers, and why function arguments sometimes change the original data.

Understanding the memory model — namespaces, references, reference counting, mutability — separates someone who writes Python from someone who truly understands it. Every concept in this series builds on this foundation.

Next in this series: Strings — Slicing, f-Strings, and String Methods


Naveen Vuppula is a Senior Data Engineering Consultant and app developer based in Ontario, Canada. He writes about Python, SQL, AWS, Azure, and everything data engineering at DriveDataScience.com.

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top
Share via
Copy link