PythonMastery
beginner 13 min read · lesson 2 of 19 in Python Fundamentals

Variables & Data Types

1 · The lesson

read

A variable in Python isn't a box you put a value into — it's a name bound to an object living somewhere in memory. That distinction sounds pedantic now; it will save you hours of confusion the first time two names appear to share a list.

This lesson covers the binding model, the naming rules the community agrees on, the handful of built-in types you'll meet on day one, and the difference between values you can mutate in place and values you can't.


1. Binding, Not Boxing

python
x = 42

Python creates the integer object 42, then makes the name x refer to it. The name lives in the current scope; the object lives in memory. Reassign and the name simply points elsewhere — the old object gets garbage-collected if nothing else refers to it.

python
x = 42
x = "now I'm a string"          # totally legal — x is just a label
x = [1, 2, 3]                   # still legal — labels are typeless

This is dynamic typing: values have types, names don't. The type lives on the object, not the variable.


2. Naming Rules and PEP 8

Python's rules for what's a legal name:

  • Letters, digits, and underscores only
  • Cannot start with a digit
  • Cannot be a reserved keyword (if, for, class, lambda, True, etc.)
  • Case-sensitive — score and Score are different names
python
user_age = 30                   # fine
_internal = "ok"                # leading underscore = "private by convention"
2nd_place = "bronze"            # SyntaxError — can't start with a digit
class = "Physics 101"           # SyntaxError — class is a keyword

PEP 8 conventions — not enforced by the interpreter, enforced by every code review you'll ever sit in:

KindConventionExample
Variable, functionsnake_caseuser_age, parse_input
ConstantUPPER_SNAKEMAX_RETRIES, PI
ClassPascalCaseHttpClient, User
"Private"leading __internal_cache

Names should describe what the value means, not what type it is. users beats user_list. count beats n.


3. The Built-in Types You'll See Every Day

The five primitives:

python
age = 30                        # int   — whole numbers, unlimited size
pi = 3.14159                    # float — decimal, ~15-17 digit precision
name = "Ada"                    # str   — text
is_active = True                # bool  — True or False (note capitals)
result = None                   # None  — "no value", Python's null

Then the four built-in collections you'll get dedicated lessons on:

python
scores = [88, 92, 75]           # list  — ordered, mutable        → ../lists/
point  = (3, 4)                 # tuple — ordered, immutable      → ../tuples/
user   = {"name": "Ada"}        # dict  — key→value mapping       → ../dictionaries/
tags   = {"python", "web"}      # set   — unique, unordered       → ../sets/

Don't memorise the collections yet — each gets its own lesson. Just know they exist and recognise the literal syntax.


4. Inspecting Types: type() and isinstance()

python
print(type(42))                 # <class 'int'>
print(type(3.14))               # <class 'float'>
print(type("hi"))               # <class 'str'>
print(type([1, 2]))             # <class 'list'>

type() is fine for quick debugging. For real checks — especially inside functions — use isinstance(), which also handles inheritance correctly:

python
x = 42
print(isinstance(x, int))       # True
print(isinstance(x, (int, float)))   # True — accepts a tuple of allowed types

You'll lean on isinstance() in the Exceptions lesson when validating inputs.


5. Type Conversion

Python won't silently convert types for you. You ask, you get:

python
int("42")                       # 42        — string → int
float("3.14")                   # 3.14      — string → float
str(3.14)                       # "3.14"    — anything → string
bool(0), bool(1), bool("")      # False, True, False

The trap:

python
int("3.14")                     # ValueError — int() won't parse a decimal string
int(float("3.14"))              # 3          — go through float first

Reading numbers from a user is where this bites first:

python
age = int(input("Age: "))       # crashes if they type "thirty"

You'll handle that properly in Errors & Exceptions.


6. Mutability — the Idea That Underpins Everything Later

Some objects can be changed in place. Some can't. This isn't a stylistic detail; it changes how assignment behaves.

Immutable — int, float, str, bool, tuple, None. Once created, the object can never change. Operations return new objects.

python
name = "ada"
name.upper()                    # returns "ADA" — but name is still "ada"
name = name.upper()             # now name points to the new string "ADA"

Mutable — list, dict, set. The object itself can be modified.

python
scores = [88, 92, 75]
scores.append(100)              # mutates the existing list
print(scores)                   # [88, 92, 75, 100]

The consequence — and this is the one that bites everyone once:

python
a = [1, 2, 3]
b = a                           # b doesn't get a copy — b is another name for the same list
b.append(99)
print(a)                        # [1, 2, 3, 99]   — surprise

= doesn't copy. It binds another name to the same object. To actually copy a list, use a.copy() or list(a). We'll come back to this in Lists.


Common Mistakes

  • Shadowing built-ins. Naming a variable list, dict, str, or type hides the built-in for the rest of that scope. list = [1, 2, 3] then list(range(5)) blows up with a TypeError. Pick items or values instead.
  • Assuming = copies. It doesn't — both names point to the same object. Only matters for mutable types, but it matters a lot there.
  • int(input(...)) with no safety net. Fine for a tutorial; not fine for anything a real human will use. Wrap it in try/except once you've read the exceptions lesson.
  • Confusing None with False or 0. They're all "falsy" (see Operators), but they're distinct objects. None means "no value yet"; 0 is a number; False is a boolean.

🎯 Your Turn — Describe Any Value

Write a function describe(value) that prints three things:

1. The type of the value (e.g. int, str, list).
2. The value itself.
3. Its length — but only if length makes sense for that type.

Calling describe("Margaret") should print the type str, the value Margaret, and length 5. Calling describe(42) should print the type and value, and skip the length line.

Skeleton:

python
def describe(value):
    # TODO 1: print the type
    # TODO 2: print the value
    # TODO 3: print the length, but only if it has one
    ...

describe("Margaret")
describe(42)
describe([1, 2, 3, 4])
describe(None)
Hint 1 — Getting a clean type name type(value).__name__ gives you the short string "int" instead of <class 'int'>. Cleaner for printing.
Hint 2 — Checking "does it have a length?" Not every object supports len(). Numbers and None don't. Two options: check the type with isinstance(value, (str, list, tuple, dict, set)), or wrap the len() call in a try/except TypeError. The isinstance route is more explicit.
Show full solution
python
def describe(value):
    print(f"Type:  {type(value).__name__}")
    print(f"Value: {value}")

    if isinstance(value, (str, list, tuple, dict, set)):
        print(f"Length: {len(value)}")

    print("-" * 20)

describe("Margaret")
describe(42)
describe([1, 2, 3, 4])
describe(None)

Output:

python
Type:  str
Value: Margaret
Length: 5
--------------------
Type:  int
Value: 42
--------------------
Type:  list
Value: [1, 2, 3, 4]
Length: 4
--------------------
Type:  NoneType
Value: None
--------------------

You've used type(), isinstance() with a tuple of types, len(), an f-string, and a conditional — five tools in twelve lines.


What You Learned

  • A variable is a name bound to an object — not a container. Names are typeless; objects carry the type.
  • PEP 8: snake_case for variables and functions, UPPER_SNAKE for constants, PascalCase for classes.
  • The five primitives — int, float, str, bool, None — plus the four collections you'll meet later.
  • type() for debugging, isinstance() for real checks.
  • Conversion is explicit: int("42"), str(3.14). int("3.14") is a ValueError — go through float first.
  • Mutability matters. = never copies; it only binds another name to the same object.

Next: Numbers in Depth — integer division, the floating-point trap, and the math module you'll actually use.

Practice this

on practicepython.in

Short exercises that run in your browser and tell you what your code actually did, not just whether a test passed.