Skip to content

Latest commit

 

History

History
303 lines (214 loc) · 7.4 KB

File metadata and controls

303 lines (214 loc) · 7.4 KB
layout default
title Pythonic Python
parent Lessons
nav_order 1
permalink /lessons/pythonic-python/
course_lesson true
course_index 01
previous_page /setup/
previous_title Setup
next_page /lessons/advanced-functions/
next_title Advanced Functions

01 - Pythonic Python

Pythonic code uses Python's strengths while remaining easy for another person to read. It does not mean writing the shortest possible code.

A simple picture

Imagine two students solving the same maths problem. One writes many repeated steps. The other notices a clear pattern and explains it in one readable line. Pythonic code is like the second solution: it uses familiar Python tools to express the idea directly.

Always choose clarity first. A five-line loop is better than a one-line expression that nobody understands.

Comprehensions

Suppose we want the square of every number. The ordinary loop is:

squares = []

for number in range(1, 6):
    squares.append(number * number)

print(squares)

Output:

[1, 4, 9, 16, 25]

A list comprehension expresses the same pattern in one line:

squares = [number * number for number in range(1, 6)]
print(squares)

Read it from left to right:

result expression     loop variable       source
number * number       for number          in range(1, 6)

Add a condition at the end when only some items should be kept:

even_numbers = [number for number in range(10) if number % 2 == 0]
print(even_numbers)

The condition is tested for every number. Only numbers that make the condition True enter the new list.

Set and dictionary comprehensions

The surrounding brackets decide the result type:

unique_lengths = {len(word) for word in ["cat", "tiger", "dog"]}
squares_by_number = {number: number ** 2 for number in range(1, 4)}

print(unique_lengths)
print(squares_by_number)

Output (set order may vary):

{3, 5}
{1: 1, 2: 4, 3: 9}

Important: {value for value in items} creates a set, but {key: value for ...} creates a dictionary.

Do not put several loops, conditions, and complex calculations into one comprehension. Write a normal loop when the intent stops being obvious.

Unpacking and useful built-ins

Unpacking places items from a collection into separate names:

point = (12, 8)
x, y = point
print(x)
print(y)

The number of names must normally match the number of items. A starred name gathers the extra items into a list:

first, *middle, last = [10, 20, 30, 40, 50]
print(first)
print(middle)
print(last)

Output:

10
[20, 30, 40]
50

Python also makes swapping values safe and clear:

left = "red"
right = "blue"
left, right = right, left

enumerate() gives position and value

Use enumerate() when a loop needs both an item and its position:

for index, name in enumerate(["Asha", "Ravi"], start=1):
    print(index, name)

start=1 changes the displayed position. It does not change the list.

zip() pairs related values

names = ["Asha", "Ravi"]
marks = [80, 92]

for name, mark in zip(names, marks):
    print(f"{name}: {mark}")

By default, zip() stops when the shortest input ends. Use strict=True when different lengths should be treated as a mistake:

names = ["Asha", "Ravi"]
marks = [80]

# list(zip(names, marks, strict=True))  # raises ValueError

any() and all() answer group questions

marks = [72, 41, 88]

someone_failed = any(mark < 50 for mark in marks)
everyone_passed = all(mark >= 40 for mark in marks)

print(someone_failed)
print(everyone_passed)

any() is True when at least one value is true. all() is True only when every value is true.

Important empty-input rule: any([]) is False, while all([]) is True. There is no failing item in an empty collection.

Sorting with a key

sorted() returns a new list. The original collection is not changed:

students = [{"name": "Ravi", "mark": 92}, {"name": "Asha", "mark": 86}]
ranked = sorted(students, key=lambda student: student["mark"], reverse=True)
print(ranked)

The key function tells Python which value controls the ordering. Python calls it once for each item.

Use .sort() only when changing the existing list is intentional:

numbers = [3, 1, 2]
result = numbers.sort()

print(numbers)
print(result)

Output:

[1, 2, 3]
None

Important: list.sort() changes the list and returns None. sorted(...) returns a new list.

Choosing clear Python tools

Common Pythonic choices include:

  • loop directly over items instead of repeatedly using indexes;
  • use enumerate() when the index is genuinely needed;
  • use zip() for related collections;
  • use in for membership;
  • use dict.get() when a missing key has a sensible default;
  • use a comprehension for one clear transformation or filter;
  • use a normal loop for several steps or side effects.

Bug Hunter

Find the problem before opening the fix.

Bug 1: the original list disappears

numbers = [3, 1, 2]
numbers = numbers.sort()
print(numbers)

Bug 2: one student silently disappears

names = ["Asha", "Ravi", "Meena"]
marks = [80, 92]

for name, mark in zip(names, marks):
    print(name, mark)

Bug 3: a clever but confusing comprehension

result = [n * 2 if n % 2 == 0 else n * 3 for n in range(20) if n > 4]
Show Bug Hunter fixes
  1. Keep the list name and call numbers.sort(), or write numbers = sorted(numbers).
  2. Use zip(names, marks, strict=True) when unequal lengths are invalid.
  3. Split the logic into a normal loop with named intermediate values when the rule needs explanation.

Practice

  1. Rewrite a loop as a list comprehension.
  2. Create a dictionary comprehension of numbers and squares.
  3. Use enumerate() to number a shopping list.
  4. Use zip() to combine names and scores.
  5. Find whether any score is below 40 with any().
  6. Find whether all scores are passing with all().
  7. Sort records by a nested value.
  8. Unpack a tuple containing a date.
  9. Flatten a two-level list with a comprehension.
  10. Refactor a small repetitive script into Pythonic functions.
Show hints
  1. Put the expression before for.
  2. Use {number: number ** 2 for number in values}.
  3. Start enumerate() at 1.
  4. Keep the sequences the same length.
  5. any(condition for item in items).
  6. all(condition for item in items).
  7. Use key=lambda record: ....
  8. Match one name to each tuple value.
  9. Use two for clauses.
  10. Remove repeated logic but do not make a clever one-liner.
Show solution ideas
  1. [value * 2 for value in values].
  2. {number: number ** 2 for number in range(1, 6)}.
  3. for position, item in enumerate(items, start=1).
  4. for name, score in zip(names, scores).
  5. any(score < 40 for score in scores).
  6. all(score >= 40 for score in scores).
  7. sorted(records, key=lambda record: record["score"]).
  8. year, month, day = date.
  9. [item for row in rows for item in row].
  10. Extract a function with named inputs and a clear return value.

Homework

Build a student report using comprehensions, enumerate(), zip(), sorting, and any()/all().

Checkpoint

Explain when a comprehension improves code and when a normal loop is clearer.