🐍 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.
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)
| Kind | Convention | Example |
|---|---|---|
| Variables & functions | snake_case | user_name, get_total() |
| Classes | PascalCase | UserProfile |
| Constants | ALL_CAPS | MAX_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.
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.
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()}
⚠️ 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:
- Create a
listofdictcontacts, each withname(str),age(int), andtags(asetof strings). - Print each contact using an f-string, e.g.
"Alice (28) — friend, work". - Use a list comprehension to collect the names of everyone under 30.
- Build a set of every tag used across all contacts (no duplicates).
- 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 avoidKeyErroron 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 againstValueError. - 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
- Python Tutorial — An Informal Introduction
- Python Docs — Built-in Types
- PEP 8 — Style Guide for Python Code
🚀 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.