Skip to main content

🐍 Python Syntax and Data Types

Python is the language that reads almost like English — and that's not an accident. In this lesson you'll meet the syntax rules that make Python code so clean, and the built-in data types you'll reach for in every backend script, API, and data pipeline you ever write.

🎯 Learning Objectives

By the end of this lesson, you will be able to:

  • Write valid Python using indentation, comments, and variable assignment
  • Identify and use Python's core built-in data types — numbers, strings, lists, tuples, dicts, and sets
  • Check types with type()/isinstance() and convert safely between them
  • Format strings with f-strings and manipulate them with common methods
  • Build collections concisely using comprehensions

Estimated Time: 35–45 minutes  •  Difficulty: Beginner

Hands-on: Build a small "contact record" processor that exercises every core data type.

In This Lesson

Why Python for the Backend?

Python is a high-level, interpreted language created by Guido van Rossum and first released in 1991. It powers the backends of Instagram, Spotify, and Pinterest, and it dominates data science and automation. The reason is consistent across all of those: Python optimizes for readability. You spend far more time reading code than writing it, and Python's design leans hard into making that reading effortless.

Its most famous rule is that indentation is part of the language. Where other languages use curly braces { } to group code, Python uses whitespace. This forces every Python file to be visually structured, which is why beginners often find Python code easier to follow.

timeline title A Short History of Python 1991 : Python 1.0 — Guido van Rossum 2008 : Python 3.0 — Unicode strings, a clean break 2016 : Python 3.6 — f-strings & type hints 2021 : Python 3.10 — structural pattern matching 2024 : Python 3.13 — faster interpreter, better REPL

Python's philosophy is captured in the "Zen of Python", which you can read at any time by running import this:

Beautiful is better than ugly.
Explicit is better than implicit.
Simple is better than complex.
Readability counts.

Think of Python as a well-organized toolbox where every tool is clearly labeled. Even if you're new to the workshop, you can find and use what you need without specialized knowledge.

Syntax Essentials

Statements and indentation

Python separates statements with newlines (no semicolons needed) and groups blocks with indentation — conventionally 4 spaces. The indentation isn't cosmetic; it's how the interpreter knows what belongs to a block.

name = "Alice"          # a simple assignment statement

if name == "Alice":
    print("Hello, Alice!")   # this line is inside the if-block
    print("Welcome back.")   # still inside the block
else:
    print("Hello, stranger!")

print("End of program.")     # back at the top level

⚠️ Mixing tabs and spaces

Never mix tabs and spaces for indentation — Python 3 raises a TabError. Configure your editor to insert 4 spaces when you press Tab, and the problem disappears forever.

Comments and docstrings

# A single-line comment starts with a hash.

"""
A triple-quoted string. When it is the first statement in a
module, function, or class, it becomes a docstring — Python's
built-in documentation, readable via help().
"""

Variables and assignment

Python is dynamically typed: you never declare a type. A variable is just a name bound to a value, and the type comes from the value itself.

name = "Bob"        # str
age = 25            # int
height = 1.85       # float
is_student = True   # bool

# Multiple assignment
x, y, z = 1, 2, 3

# Swap without a temp variable — a Python signature move
a, b = 5, 10
a, b = b, a         # a is now 10, b is now 5

Naming conventions (PEP 8)

KindConventionExample
Variables & functionssnake_caseuser_name, get_total()
ClassesPascalCaseUserProfile
ConstantsALL_CAPSMAX_RETRIES
"Private" attributes_leading_underscore_cache

The interpreter doesn't enforce these, but the entire Python community follows them — matching the convention makes your code instantly familiar to other developers.

Built-in Data Types

Python ships with a small set of built-in types that cover the vast majority of everyday programming. It helps to picture them as different kinds of containers in a kitchen.

Python's core built-in data types Six boxes grouping Python types by category: numbers, boolean, strings, ordered sequences, key-value mappings, and unique sets, each marked mutable or immutable. Numbers int · float · complex immutable Text str immutable sequence Sequences list · tuple · range list = mutable Mapping dict key → value, mutable Sets set · frozenset unique items None & bool None · True · False absence & truth
Figure 1 — Python's built-in types, grouped by role. The single most important distinction is mutable (can change in place, like list/dict/set) vs immutable (fixed once created, like int/str/tuple).

Numbers and booleans

a = 42        # int
b = 3.14159   # float
c = 2 + 3j    # complex

float(a)      # 42.0
int(b)        # 3  (truncates toward zero, does NOT round)

is_valid = True     # bool is a subtype of int: True == 1, False == 0
print(bool(0), bool(42), bool(""), bool("Hi"), bool([]))
# False True False True False

📖 "Truthiness"

Every Python value is either truthy or falsy in a boolean context. Falsy values include 0, 0.0, "", [], {}, set(), and None. Everything else is truthy. This lets you write if items: instead of if len(items) > 0:.

Sequences: str, list, tuple, range

# Strings — immutable
greeting = "Hello, World!"
multiline = """spans
multiple lines"""

# Lists — mutable, ordered
fruits = ["apple", "banana", "cherry"]
fruits.append("orange")   # add
fruits[0] = "pear"        # replace in place

# Tuples — immutable, ordered (great for fixed records)
point = (10, 20)
# point[0] = 5            # TypeError: tuples can't be modified

# range — a lazy, memory-efficient sequence of numbers
for n in range(1, 10, 2):
    print(n)              # 1 3 5 7 9

dict and set

# Dictionary — the workhorse of Python backends (think JSON)
person = {"name": "Charlie", "age": 30}
person["age"] = 31              # update
person["city"] = "NYC"         # add a new key
email = person.get("email", "n/a")   # safe access with a default

# Set — unordered collection of unique items
colors = {"red", "green", "blue"}
colors.add("red")               # duplicate ignored

primary = {"red", "yellow", "blue"}
secondary = {"green", "blue"}
primary | secondary             # union
primary & secondary             # intersection -> {"blue"}
primary - secondary             # difference

Strings are like recipe cards, lists are adjustable spice racks, tuples are sealed food packages, and dictionaries are labeled storage bins you look items up in by name.

Type Checking & Conversion

Because Python is dynamically typed, you sometimes need to inspect or convert a value's type at runtime — especially when data arrives as text from a web request or a file.

# Inspecting a type
print(type("David"))            # <class 'str'>
print(isinstance(40, str))      # False
print(isinstance(40, (int, float)))  # True — accepts a tuple of types

# Converting (casting) between types
age = int("42")                 # str -> int
price = float(age)              # int -> float
label = str(price)              # float -> str

# Container conversions
nums = [1, 2, 3, 2, 1]
tuple(nums)                     # (1, 2, 3, 2, 1)
set(nums)                       # {1, 2, 3}  — duplicates removed
dict([("a", 1), ("b", 2)])      # {"a": 1, "b": 2}

Not every conversion succeeds. int("hello") raises a ValueError, so real code guards against it.

flowchart TD A["str '123'"] -->|"int()"| B["int 123 ✓"] C["str 'hello'"] -->|"int()"| D["ValueError ✗"] E["int 456"] -->|"str()"| F["str '456' ✓"] G["list [1,2,3]"] -->|"tuple()"| H["tuple (1,2,3) ✓"]

Type conversion is like currency exchange: converting dollars to euros follows rules and can lose precision, so you convert deliberately and check the result.

Strings & f-Strings

Strings are everywhere in web work — URLs, JSON, HTML, log lines. Python gives you slicing, a rich method set, and modern formatting.

Indexing and slicing

text = "Python"
text[0]      # 'P'  (0-based)
text[-1]     # 'n'  (negative counts from the end)
text[1:4]    # 'yth' (start inclusive, end exclusive)
text[::-1]   # 'nohtyP' (reverse with a step of -1)

Common methods

message = "   Hello, World!   "
message.strip()             # "Hello, World!"  — trim whitespace
message.upper()             # "   HELLO, WORLD!   "
"World" in message          # True — membership test
message.replace("World", "Python")
words = message.strip().split(", ")   # ["Hello", "World!"]
", ".join(words)            # "Hello, World!"

f-strings — the modern default

Since Python 3.6, formatted string literals (f-strings) are the clearest way to build strings. Prefix with f and drop expressions inside { }.

name = "Frank"
age = 35
f"My name is {name} and I am {age} years old."

# Inline expressions
f"In 5 years, {name} will be {age + 5}."

# Format specifiers after a colon
pi = 3.14159265
f"Pi to 2 dp: {pi:.2f}"        # "Pi to 2 dp: 3.14"
f"{1234567:,}"                 # "1,234,567" — thousands separator

# Self-documenting debug form (3.8+)
f"{age=}"                      # "age=35"

✅ Prefer f-strings

You'll still see older "...".format() and C-style "%s" % formatting in existing code, and they're fine to read. For new code, reach for f-strings first — they're faster and far easier to scan.

Comprehensions

A comprehension builds a collection from an existing iterable in a single, readable expression. It's one of the most distinctly "Pythonic" features you'll use daily.

# The long way
squares = []
for x in range(10):
    squares.append(x ** 2)

# The comprehension — same result, one line
squares = [x ** 2 for x in range(10)]

# With a filter condition
even_squares = [x ** 2 for x in range(10) if x % 2 == 0]  # [0, 4, 16, 36, 64]

# Dictionary comprehension
square_map = {x: x ** 2 for x in range(5)}   # {0:0, 1:1, 2:4, 3:9, 4:16}

# Pair two lists together with zip()
names = ["Alice", "Bob", "Charlie"]
ages = [25, 30, 35]
people = {n: a for n, a in zip(names, ages)}

# Set comprehension — unique letters
letters = {c for c in "hello world" if c.isalpha()}
flowchart LR A[Input iterable] -->|for each item| B[Transform] B -->|if condition| C[New collection]

⚠️ Keep them readable

Comprehensions shine for simple map/filter work. If you find yourself nesting three loops and two conditions into one line, stop — a plain for loop is clearer. Readability counts.

Hands-on Exercise

🏋️ Contact Record Processor

Objective: Touch every core data type in one small, realistic script.

Instructions:

  1. Create a list of dict contacts, each with name (str), age (int), and tags (a set of strings).
  2. Print each contact using an f-string, e.g. "Alice (28) — friend, work".
  3. Use a list comprehension to collect the names of everyone under 30.
  4. Build a set of every tag used across all contacts (no duplicates).
  5. Compute the average age and format it to one decimal place.
💡 Hint

To collect all tags, start with an empty set and update() it from each contact's tags, or use a nested comprehension: {tag for c in contacts for tag in c["tags"]}. For the average, sum(...) / len(...) then format with f"{avg:.1f}".

✅ Sample solution
contacts = [
    {"name": "Alice", "age": 28, "tags": {"friend", "work"}},
    {"name": "Bob", "age": 34, "tags": {"work"}},
    {"name": "Charlie", "age": 22, "tags": {"gym", "friend"}},
]

# 2. Print each contact
for c in contacts:
    tags = ", ".join(sorted(c["tags"]))
    print(f"{c['name']} ({c['age']}) — {tags}")

# 3. Names of everyone under 30
young = [c["name"] for c in contacts if c["age"] < 30]
print("Under 30:", young)

# 4. Every unique tag
all_tags = {tag for c in contacts for tag in c["tags"]}
print("All tags:", all_tags)

# 5. Average age
avg = sum(c["age"] for c in contacts) / len(contacts)
print(f"Average age: {avg:.1f}")

🎯 Quick Quiz

Question 1: Which of these types is immutable (cannot be changed after creation)?

Question 2: What does int(3.99) return?

Question 3: Which expression produces a list of even numbers from 0 to 8?

Best Practices

✅ Do

  • Use 4-space indentation and follow PEP 8 naming (snake_case for variables).
  • Reach for f-strings for all new string formatting.
  • Use dict.get(key, default) to avoid KeyError on missing keys.
  • Prefer truthiness checks (if items:) over length comparisons.

⚠️ Don't

  • Don't mix tabs and spaces — it raises a TabError.
  • Don't assume int() rounds; it truncates.
  • Don't cram deeply nested logic into one comprehension.
  • Don't use a mutable default like [] as a shared collector without understanding aliasing.

Summary & Quiz

🎉 Key Takeaways

  • Python uses indentation (4 spaces) to define blocks and is dynamically typed.
  • Core types split into numbers, text, sequences (list/tuple/range), mappings (dict), and sets — each mutable or immutable.
  • Check types with type()/isinstance() and convert deliberately, guarding against ValueError.
  • f-strings are the modern way to format text; strings offer rich slicing and methods.
  • Comprehensions build lists, dicts, and sets in one readable expression.

📚 Further Reading

🚀 What's Next?

Now that you can store and shape data, the next lesson brings your code to life with control structures and functions — the if/for/while logic and reusable def blocks that turn data into behavior.

🎉 Great start!

You've got Python's vocabulary. Time to teach it how to make decisions.