Python & AI Tutorials Logo
Python Programming

37. Built-in Functions and Useful Tools

Python provides a rich collection of built-in functions that are always available without needing to import any modules. These functions form the foundation of everyday Python programming, helping you work efficiently with data, sequences, and collections. In this chapter, we'll explore Python's most useful built-in tools and learn how to leverage them to write cleaner, more expressive code.

Understanding Python's Type System

Before diving into specific built-in functions, it's helpful to understand how Python organizes its data types. This knowledge will help you predict which operations work with which types and understand error messages when they occur.

Python's data types can be understood from two complementary perspectives:

Type Hierarchy: How Types Are Related

object

Numeric Types

Sequence Types

Mapping Types

Set Types

Other

int

float

complex

Immutable

Mutable

str

tuple

range

list

dict

set

frozenset

bool

NoneType

This shows how Python organizes types into families based on what they ARE.

Capability-Based View: What Types Can Do

For built-in functions, what matters more is what types can DO:

All Python Objects

Iterable
(can loop with for)

Not Iterable
(int, float, bool)

Collection
(has length)

Other Iterables
(generator, iterator)

Sequence
(ordered, indexed)

Unordered
(set, dict)

str

list

tuple

range

set

dict

Key capabilities:

  • Iterable: Can be used in for loops → Works with sum(), any(), all(), sorted()
  • Collection: Iterable with len() → Works with len() and in operator
  • Sequence: Collection with indexing → Supports [index] and slicing [start:end]

Why This Matters

Built-in functions require specific capabilities:

FunctionRequiresWorks With
len()Collectionstr, list, dict, set, tuple
sum()Iterable of numberslist, tuple, set, range, generator
sorted()Iterablestr, list, dict, set, tuple
[index]Sequencestr, list, tuple, range

Understanding these categories helps you:

  • Predict which functions work with which types
  • Understand error messages like "object is not iterable"
  • Know when you can index ([0]) vs when you can only loop (for)

37.1) Common Built-in Functions (len, sum, min, max, abs, round)

Python's most frequently used built-in functions help you perform common operations on data without writing loops or complex logic. These functions are optimized, readable, and form the foundation of Pythonic code.

37.1.1) Measuring Length with len()

The len() function returns the number of items in a collection. It works with strings, lists, tuples, dictionaries, sets, and any other collection type.

python
# Counting characters in a string
message = "Hello, World!"
print(len(message))  # Output: 13
 
# Counting elements in a list
scores = [85, 92, 78, 90, 88]
print(len(scores))   # Output: 5
 
# Counting key-value pairs in a dictionary
student = {"name": "Bob", "age": 21, "major": "CS"}
print(len(student))  # Output: 3
 
# Counting unique items in a set
unique_ids = {101, 102, 103, 101, 102}  # Duplicates removed
print(len(unique_ids))  # Output: 3

The len() function is particularly useful when you need to know the size of data before processing it:

python
# Processing data based on size
data = [12, 45, 23, 67, 89, 34]
 
if len(data) < 5:
    print("Not enough data for analysis")
else:
    print(f"Analyzing {len(data)} data points")  # Output: Analyzing 6 data points
    average = sum(data) / len(data)
    print(f"Average: {average}")  # Output: Average: 45.0

37.1.2) Calculating Totals with sum()

The sum() function adds up all numbers in an iterable. It's much cleaner than writing a loop to accumulate values.

python
# Summing a list of numbers
prices = [19.99, 24.50, 15.75, 32.00]
total = sum(prices)
print(f"Total: ${total}")  # Output: Total: $92.24
 
# Summing a tuple
daily_steps = (8500, 10200, 7800, 9500, 11000)
weekly_total = sum(daily_steps)
print(f"Total steps this week: {weekly_total}")  # Output: Total steps this week: 47000
 
# Summing a range
total_1_to_100 = sum(range(1, 101))
print(total_1_to_100)  # Output: 5050

A practical example combining sum() and len() to calculate averages:

python
# Calculate average test score
test_scores = [88, 92, 79, 85, 90, 87]
 
total_score = sum(test_scores)
num_tests = len(test_scores)
average_score = total_score / num_tests
 
print(f"Average score: {average_score:.1f}")  # Output: Average score: 86.8

Important limitation: sum() only works with numbers. You cannot use it to concatenate strings or combine lists:

python
# This raises TypeError
words = ["Hello", " ", "World"]
# sentence = sum(words)  # TypeError: unsupported operand type(s)

37.1.3) Finding Extremes with min() and max()

The min() and max() functions find the smallest and largest values in an iterable. They work with numbers, strings, and any objects that can be compared.

python
# Finding minimum and maximum numbers
temperatures = [72, 68, 75, 70, 73, 69]
coldest = min(temperatures)
warmest = max(temperatures)
print(f"Temperature range: {coldest}°F to {warmest}°F")
# Output: Temperature range: 68°F to 75°F
 
# Finding minimum and maximum strings (alphabetically)
names = ["Zoe", "Alice", "Bob", "Charlie"]
first_alphabetically = min(names)
last_alphabetically = max(names)
print(f"First: {first_alphabetically}, Last: {last_alphabetically}")
# Output: First: Alice, Last: Zoe

You can also pass multiple arguments directly instead of a collection:

python
# Comparing individual values
lowest = min(45, 23, 67, 12, 89)
highest = max(45, 23, 67, 12, 89)
print(f"Lowest: {lowest}, Highest: {highest}")
# Output: Lowest: 12, Highest: 89
 
# Useful for comparing a few specific values
price1 = 19.99
price2 = 24.50
price3 = 15.75
cheapest = min(price1, price2, price3)
print(f"Cheapest option: ${cheapest}")  # Output: Cheapest option: $15.75

37.1.4) Getting Absolute Values with abs()

The abs() function returns the absolute value of a number—its distance from zero, always positive. This is useful when you care about magnitude but not direction.

python
# Absolute value of negative numbers
print(abs(-42))      # Output: 42
print(abs(-3.14))    # Output: 3.14
 
# Absolute value of positive numbers (unchanged)
print(abs(42))       # Output: 42
print(abs(3.14))     # Output: 3.14
 
# Absolute value of zero
print(abs(0))        # Output: 0

A common use case is calculating differences where direction doesn't matter:

python
# Calculate temperature change (magnitude only)
morning_temp = 65
evening_temp = 72
temperature_change = abs(evening_temp - morning_temp)
print(f"Temperature changed by {temperature_change}°F")
# Output: Temperature changed by 7°F

37.1.5) Rounding Numbers with round()

The round() function rounds a number to a specified number of decimal places. With no second argument, it rounds to the nearest integer.

python
# Rounding to nearest integer
print(round(3.7))     # Output: 4
print(round(3.2))     # Output: 3
print(round(3.5))     # Output: 4
print(round(4.5))     # Output: 4  (rounds to nearest even number)
 
# Rounding to specific decimal places
price = 19.876
print(round(price, 2))  # Output: 19.88  (2 decimal places)
print(round(price, 1))  # Output: 19.9   (1 decimal place)
 
# Rounding to negative decimal places (rounds to tens, hundreds, etc.)
population = 1234567
print(round(population, -3))  # Output: 1235000  (nearest thousand)
print(round(population, -4))  # Output: 1230000  (nearest ten thousand)

Note about halfway values: When rounding a number that's exactly halfway between two integers, Python has a special rule. For example, 2.5 is exactly halfway between 2 and 3. You might expect it to round up to 3, but Python rounds to whichever neighbor is an even number—in this case, 2.

This is called "banker's rounding" or "round half to even." It's part of the IEEE 754 standard and helps reduce bias over many rounding operations.

python
# Halfway values round to the nearest even number
print(round(0.5))   # Output: 0 (0 is even)
print(round(1.5))   # Output: 2 (2 is even)
print(round(2.5))   # Output: 2 (2 is even)
print(round(3.5))   # Output: 4 (4 is even)
print(round(4.5))   # Output: 4 (4 is even)

37.2) Enumerating Sequences with enumerate()

When looping through a sequence, you often need both the element and its position. The enumerate() function provides both, eliminating the need for manual counter variables.

37.2.1) The Problem with Manual Counters

Before learning about enumerate(), programmers often use a counter variable to track position:

python
# Manual counter approach (works but not ideal)
fruits = ["apple", "banana", "cherry", "date"]
index = 0
 
for fruit in fruits:
    print(f"{index}: {fruit}")
    index += 1
# Output:
# 0: apple
# 1: banana
# 2: cherry
# 3: date

This approach has several drawbacks:

  • Extra variable to manage (index)
  • Easy to forget incrementing the counter

37.2.2) Using enumerate() for Position and Value

The enumerate() function solves this problem elegantly. It takes an iterable and returns pairs of (index, element):

python
# Using enumerate() - cleaner and more Pythonic
fruits = ["apple", "banana", "cherry", "date"]
 
for index, fruit in enumerate(fruits):
    print(f"{index}: {fruit}")
# Output:
# 0: apple
# 1: banana
# 2: cherry
# 3: date

The syntax for index, fruit in enumerate(fruits) uses tuple unpacking (as we learned in Chapter 15). Each iteration, enumerate() provides a tuple like (0, "apple"), which gets unpacked into the variables index and fruit.

Here's what enumerate() actually produces:

python
# Seeing enumerate's output directly
fruits = ["apple", "banana", "cherry"]
enumerated = list(enumerate(fruits))
print(enumerated)
# Output: [(0, 'apple'), (1, 'banana'), (2, 'cherry')]

37.2.3) Starting Enumeration from a Different Number

By default, enumerate() starts counting from 0. You can specify a different starting number with the start parameter:

python
# Start counting from 1 (useful for display)
tasks = ["Write code", "Test code", "Deploy code"]
 
for number, task in enumerate(tasks, start=1):
    print(f"Step {number}: {task}")
# Output:
# Step 1: Write code
# Step 2: Test code
# Step 3: Deploy code

This is particularly useful when displaying numbered lists to users, who typically expect counting to start from 1:

python
# Menu with numbered options
menu_items = ["New Game", "Load Game", "Settings", "Quit"]
 
print("Main Menu:")
for number, item in enumerate(menu_items, start=1):
    print(f"{number}. {item}")
# Output:
# Main Menu:
# 1. New Game
# 2. Load Game
# 3. Settings
# 4. Quit

37.2.4) enumerate() with Strings and Other Iterables

The enumerate() function works with any iterable, not just lists:

python
# Enumerating characters in a string
word = "Python"
 
for position, letter in enumerate(word):
    print(f"Letter {position}: {letter}")
# Output:
# Letter 0: P
# Letter 1: y
# Letter 2: t
# Letter 3: h
# Letter 4: o
# Letter 5: n
 
# Enumerating a tuple
coordinates = (10, 20, 30, 40)
 
for index, value in enumerate(coordinates):
    print(f"Coordinate {index}: {value}")
# Output:
# Coordinate 0: 10
# Coordinate 1: 20
# Coordinate 2: 30
# Coordinate 3: 40

The enumerate() function makes code more readable and less error-prone. Whenever you need both position and value in a loop, reach for enumerate() instead of managing a counter manually.

37.3) Combining Sequences with zip()

The zip() function combines multiple iterables element-by-element, creating pairs (or tuples) of corresponding elements. This is invaluable when you need to process related data from separate sequences simultaneously.

37.3.1) Understanding How zip() Works

The zip() function takes two or more iterables and returns an iterator of tuples, where each tuple contains one element from each input iterable:

python
# Combining two lists
names = ["Alice", "Bob", "Charlie"]
ages = [25, 30, 35]
 
combined = list(zip(names, ages))
print(combined)
# Output: [('Alice', 25), ('Bob', 30), ('Charlie', 35)]

The name "zip" comes from the zipper on clothing—it combines two separate sides into one joined structure, element by element.

Here's a visual representation of how zip() pairs elements:

names
['Alice', 'Bob', 'Charlie']

zip()

ages
[25, 30, 35]

[('Alice', 25),
('Bob', 30),
('Charlie', 35)]

37.3.2) Using zip() in Loops

The most common use of zip() is in for loops, where you need to iterate over multiple sequences simultaneously:

python
# Processing parallel data
students = ["Alice", "Bob", "Charlie", "Diana"]
scores = [92, 85, 88, 95]
 
for student, score in zip(students, scores):
    print(f"{student} scored {score}")
# Output:
# Alice scored 92
# Bob scored 85
# Charlie scored 88
# Diana scored 95

This is much cleaner than using indices:

python
# Without zip() - more complex and error-prone
students = ["Alice", "Bob", "Charlie", "Diana"]
scores = [92, 85, 88, 95]
 
for i in range(len(students)):
    print(f"{students[i]} scored {scores[i]}")
# Same output, but more code and potential for index errors

37.3.3) Handling Sequences of Different Lengths

When the input sequences have different lengths, zip() stops when the shortest sequence is exhausted:

python
# Unequal length sequences
names = ["Alice", "Bob", "Charlie", "Diana"]
ages = [25, 30]  # Only 2 ages
 
for name, age in zip(names, ages):
    print(f"{name} is {age} years old")
# Output:
# Alice is 25 years old
# Bob is 30 years old
# (Charlie and Diana are not processed)

This behavior prevents errors but can lead to silent data loss if you're not careful. Always verify that your sequences have the expected lengths:

python
# Checking for length mismatch
names = ["Alice", "Bob", "Charlie"]
ages = [25, 30]
 
if len(names) != len(ages):
    print(f"Warning: {len(names)} names but {len(ages)} ages")
    print("Only processing the first", min(len(names), len(ages)), "entries")
    # Output: Warning: 3 names but 2 ages
    # Output: Only processing the first 2 entries
 
# Continue with zip() - it will stop at the shortest
for name, age in zip(names, ages):
    print(f"{name} is {age} years old")

37.3.4) Zipping More Than Two Sequences

The zip() function can combine any number of iterables:

python
# Combining three sequences
products = ["Laptop", "Mouse", "Keyboard"]
prices = [999.99, 24.99, 79.99]
quantities = [5, 20, 15]
 
print("Inventory Report:")
for product, price, quantity in zip(products, prices, quantities):
    total_value = price * quantity
    print(f"{product}: ${price} × {quantity} = ${total_value:.2f}")
# Output:
# Inventory Report:
# Laptop: $999.99 × 5 = $4999.95
# Mouse: $24.99 × 20 = $499.80
# Keyboard: $79.99 × 15 = $1199.85

37.3.5) Creating Dictionaries with zip()

A powerful pattern is using zip() to create dictionaries from separate key and value sequences:

python
# Creating a dictionary from two lists
keys = ["name", "age", "city"]
values = ["Alice", 25, "Boston"]
 
person = dict(zip(keys, values))
print(person)
# Output: {'name': 'Alice', 'age': 25, 'city': 'Boston'}

37.4) Boolean Aggregation with any() and all()

The any() and all() functions test conditions across entire iterables, returning a single boolean result. They're powerful tools for validation and decision-making based on multiple conditions.

37.4.1) Understanding any(): True if At Least One Element is True

The any() function returns True if at least one element in an iterable is truthy (evaluates to True). If all elements are falsy, it returns False:

python
# Basic any() examples
print(any([True, False, False]))   # Output: True  (at least one True)
print(any([False, False, False]))  # Output: False (all False)
print(any([False, True, True]))    # Output: True  (multiple True values)
 
# Empty iterables
print(any([]))  # Output: False (no elements to be True)

The any() function uses Python's truthiness rules (as we learned in Chapter 7). Non-zero numbers, non-empty strings, and non-empty collections are truthy:

python
# any() with different truthy/falsy values
print(any([0, 0, 1]))           # Output: True  (1 is truthy)
print(any([0, 0, 0]))           # Output: False (all zeros are falsy)
print(any(["", "", "text"]))    # Output: True  ("text" is truthy)
print(any(["", "", ""]))        # Output: False (empty strings are falsy)

37.4.2) Practical Uses of any()

Example: Checking if any condition is met

python
# Check if any score is failing (below 60)
scores = [75, 82, 55, 90, 88]
has_failing_grade = any(score < 60 for score in scores)
 
if has_failing_grade:
    print("Warning: At least one failing grade")
    # Output: Warning: At least one failing grade
else:
    print("All grades are passing")

37.4.3) Understanding all(): True Only if All Elements are True

The all() function returns True only if all elements in an iterable are truthy. If any element is falsy, it returns False:

python
# Basic all() examples
print(all([True, True, True]))    # Output: True  (all True)
print(all([True, False, True]))   # Output: False (one False)
print(all([True, True, False]))   # Output: False (one False)
 
# Empty iterables
print(all([]))  # Output: True (vacuous truth - no False elements)

The behavior with empty iterables might seem surprising: all([]) returns True. This is called vacuous truth—the statement "all elements are True" is technically true when there are no elements to contradict it.

python
# all() with different truthy/falsy values
print(all([1, 2, 3]))           # Output: True  (all non-zero)
print(all([1, 0, 3]))           # Output: False (0 is falsy)
print(all(["a", "b", "c"]))     # Output: True  (all non-empty)
print(all(["a", "", "c"]))      # Output: False (empty string is falsy)

37.4.4) Practical Uses of all()

Example: Validating that all conditions are met

python
# Check if all scores are passing (60 or above)
scores = [75, 82, 68, 90, 88]
all_passing = all(score >= 60 for score in scores)
 
if all_passing:
    print("Congratulations! All grades are passing")
    # Output: Congratulations! All grades are passing
else:
    print("Some grades need improvement")

37.4.5) Short-Circuit Evaluation in any() and all()

Both any() and all() use short-circuit evaluation (as we learned in Chapter 9). They stop checking as soon as the result is determined:

python
# Function that prints when called (to show execution)
def is_positive(n):
    print(f"Checking {n}")
    return n > 0
 
# any() stops at first True
print("Testing any():")
numbers = [0, 0, 1, 2, 3]
result = any(is_positive(n) for n in numbers)
# Output:
# Testing any():
# Checking 0
# Checking 0
# Checking 1
# (Stops here - doesn't check 2 or 3)
print(f"Result: {result}")  # Output: Result: True
 
print("\nTesting all():")
numbers = [1, 2, 0, 3, 4]
result = all(is_positive(n) for n in numbers)
# Output:
# Testing all():
# Checking 1
# Checking 2
# Checking 0
# (Stops here - doesn't check 3 or 4)
print(f"Result: {result}")  # Output: Result: False

This makes any() and all() efficient—they don't waste time checking elements after the result is determined.

37.5) Sorting with sorted() and Custom Keys

The sorted() function creates a new sorted list from any iterable. Unlike the .sort() method (which only works on lists and modifies them in place), sorted() works with any iterable and always returns a new list.

37.5.1) Basic Sorting with sorted()

The sorted() function arranges elements in ascending order by default:

python
# Sorting numbers
numbers = [42, 17, 93, 8, 55]
sorted_numbers = sorted(numbers)
print(sorted_numbers)  # Output: [8, 17, 42, 55, 93]
 
# Original list is unchanged
print(numbers)  # Output: [42, 17, 93, 8, 55]
 
# Sorting strings (alphabetically)
names = ["Charlie", "Alice", "Bob", "Diana"]
sorted_names = sorted(names)
print(sorted_names)  # Output: ['Alice', 'Bob', 'Charlie', 'Diana']

The sorted() function works with any iterable, not just lists:

python
# Sorting a tuple (returns a list)
coordinates = (5, 2, 8, 1, 9)
sorted_coords = sorted(coordinates)
print(sorted_coords)  # Output: [1, 2, 5, 8, 9]
 
# Sorting a string (returns list of characters)
word = "python"
sorted_letters = sorted(word)
print(sorted_letters)  # Output: ['h', 'n', 'o', 'p', 't', 'y']
 
# Sorting a set (returns a sorted list)
unique_numbers = {5, 8, 2, 1}
sorted_unique = sorted(unique_numbers)
print(sorted_unique)  # Output: [1, 2, 5, 8]

37.5.2) Reverse Sorting

Use the reverse=True parameter to sort in descending order:

python
# Descending order for numbers
scores = [85, 92, 78, 95, 88]
highest_first = sorted(scores, reverse=True)
print(highest_first)  # Output: [95, 92, 88, 85, 78]
 
# Descending order for strings (reverse alphabetical)
names = ["Charlie", "Alice", "Bob", "Diana"]
reverse_alpha = sorted(names, reverse=True)
print(reverse_alpha)  # Output: ['Diana', 'Charlie', 'Bob', 'Alice']

37.5.3) Understanding the key Parameter

The key parameter is where sorted() becomes truly powerful. It transforms how Python compares elements during sorting.

What is the key parameter?

The key parameter accepts a function. Python calls this function on each element to extract a "comparison key," then sorts based on these keys instead of the original elements.

How it works step-by-step:

  1. Python calls the key function on each element
  2. Python collects all the keys
  3. Python sorts by comparing these keys
  4. Python returns the original elements in the new order
python
# Example: Sorting by length
words = ["python", "is", "awesome"]
 
# Step 1: Python calls len() on each word
# len("python")  → 6
# len("is")      → 2
# len("awesome") → 7
 
# Step 2: Python has these keys: [6, 2, 7]
 
# Step 3: Python sorts the keys: [2, 6, 7]
 
# Step 4: Python returns words in that order: ["is", "python", "awesome"]
 
result = sorted(words, key=len)
print(result)  # Output: ['is', 'python', 'awesome']

Visualizing the key function:

Original:
['python', 'is', 'awesome']

Apply key=len

Keys:
[6, 2, 7]

Sort keys

Sorted keys:
[2, 6, 7]

Return elements:
['is', 'python', 'awesome']

What can be a key function?

The key function must:

  • Accept one argument (the element being sorted)
  • Return a value that Python can compare (numbers, strings, tuples, etc.)
python
# Built-in functions work great
sorted(numbers, key=abs)        # Sort by absolute value
sorted(words, key=len)          # Sort by length
sorted(names, key=str.lower)    # Sort case-insensitively
 
# Your own functions
def first_letter(word):
    return word[0]
 
sorted(words, key=first_letter)  # Sort by first letter
 
# Lambda functions (Chapter 23)
sorted(words, key=lambda w: w[-1])  # Sort by last letter

Important: The key function is called once per element

python
# Demonstrating when key function is called
def show_key(word):
    print(f"Getting key for: {word}")
    return len(word)
 
words = ["cat", "elephant", "dog"]
result = sorted(words, key=show_key)
# Output:
# Getting key for: cat
# Getting key for: elephant
# Getting key for: dog
 
print(result)  # Output: ['cat', 'dog', 'elephant']

Important: The key function is called once per element

Notice that show_key is called exactly once for each word, not repeatedly during comparisons. Python is efficient—it extracts all keys first, caches them, then sorts using the cached keys.

Think of key as answering: "What aspect should I compare?"

  • key=len → "Compare by length"
  • key=abs → "Compare by absolute value"
  • key=str.lower → "Compare as if all lowercase"
  • key=lambda x: x[1] → "Compare by second element"

The key parameter lets you sort by any property of your elements, making sorted() incredibly versatile.

37.5.4) Sorting with Built-in Functions as Keys

Python's built-in functions make excellent key functions:

python
# Sorting by absolute value
numbers = [-5, 2, -8, 1, -3, 7]
sorted_by_magnitude = sorted(numbers, key=abs)
print(sorted_by_magnitude)  # Output: [1, 2, -3, -5, 7, -8]
 
# Sorting strings case-insensitively
names = ["alice", "Bob", "CHARLIE", "diana"]
sorted_case_insensitive = sorted(names, key=str.lower)
print(sorted_case_insensitive)  # Output: ['alice', 'Bob', 'CHARLIE', 'diana']

37.5.5) Sorting Complex Data Structures

When sorting lists of tuples or lists, you can use indexing to specify which element to sort by:

python
# Sorting tuples by second element
students = [
    ("Alice", 92),
    ("Bob", 85),
    ("Charlie", 88),
    ("Diana", 95)
]
 
# Sort by score (second element)
by_score = sorted(students, key=lambda student: student[1])
print(by_score)
# Output: [('Bob', 85), ('Charlie', 88), ('Alice', 92), ('Diana', 95)]
 
# Sort by score descending
by_score_desc = sorted(students, key=lambda student: student[1], reverse=True)
print(by_score_desc)
# Output: [('Diana', 95), ('Alice', 92), ('Charlie', 88), ('Bob', 85)]

Note: We're using lambda here (as we learned in Chapter 23). A lambda is a small anonymous function. The expression lambda student: student[1] creates a function that takes a student tuple and returns its second element (the score).

37.5.6) Multi-Level Sorting

You can sort by multiple criteria by returning a tuple from the key function. Python compares tuples element by element, from left to right:

How tuple comparison works:

When Python compares two tuples, it follows these rules:

  1. Compare the first elements. If they're different, the comparison is done.
  2. If the first elements are equal, compare the second elements.
  3. If the second elements are equal, compare the third elements.
  4. Continue until finding a difference or running out of elements.
python
# Examples of tuple comparison
print((1, 'a') < (2, 'z'))   # Output: True  (1 < 2, so True immediately)
print((1, 'z') < (1, 'a'))   # Output: False (1 == 1, so compare 'z' < 'a')
print((1, 'a') < (1, 'a'))   # Output: False (both tuples are equal)
print((1, 2, 9) < (1, 3, 1)) # Output: True  (1 == 1, then 2 < 3)

This makes tuples perfect for multi-level sorting—Python automatically handles the "compare first criterion, then second, then third" logic for you:

python
# Sort by multiple criteria
students = [
    ("Alice", "Smith", 92),
    ("Bob", "Jones", 85),
    ("Alice", "Brown", 88),
    ("Charlie", "Smith", 85)
]
 
# Sort by first name, then last name
by_name = sorted(students, key=lambda s: (s[0], s[1]))
print("By name:")
for student in by_name:
    print(f"  {student}")
# Output:
# By name:
#   ('Alice', 'Brown', 88)
#   ('Alice', 'Smith', 92)
#   ('Bob', 'Jones', 85)
#   ('Charlie', 'Smith', 85)
 
# Sort by score descending, then name ascending
by_score_then_name = sorted(students, key=lambda s: (-s[2], s[0]))
print("\nBy score (high to low), then name:")
for student in by_score_then_name:
    print(f"  {student}")
# Output:
# By score (high to low), then name:
#   ('Alice', 'Smith', 92)
#   ('Alice', 'Brown', 88)
#   ('Bob', 'Jones', 85)
#   ('Charlie', 'Smith', 85)

Note: To sort one criterion descending and another ascending, we negate the numeric value (-s[2]). This works because negating reverses the sort order for numbers. In the example above, -s[2] sorts scores from highest to lowest, while s[0] sorts names from A to Z.

37.5.7) Using Helper Functions for Complex Keys

When the sorting logic becomes complex, defining a helper function makes the code more readable and maintainable. You can then use this helper function inside your key function.

Example: Sorting files by extension

Suppose you want to group files by their extension (.csv, .jpg, .pdf, etc.), and within each group, sort alphabetically by filename. The key function needs to extract the file extension, which requires some string manipulation.

python
# Sort files by extension, then by name
files = [
    "report.pdf",
    "data.csv",
    "image.jpg",
    "notes.txt",
    "backup.csv",
    "photo.jpg"
]
 
# Extract extension for sorting
def get_extension(filename):
    """Extract the file extension from a filename."""
    return filename.split(".")[-1]  # Split by "." and get the last part
 
# Use the helper function in the key
sorted_files = sorted(files, key=lambda f: (get_extension(f), f))
print("Files sorted by extension, then name:")
for file in sorted_files:
    print(f"  {file}")
# Output:
# Files sorted by extension, then name:
#   backup.csv      # csv files first (alphabetically)
#   data.csv        # csv files first (alphabetically)
#   image.jpg       # jpg files next
#   photo.jpg       # jpg files next
#   report.pdf      # pdf files next
#   notes.txt       # txt files last

How it works:

  1. The key function lambda f: (get_extension(f), f) returns a tuple for each filename
  2. For "report.pdf", it returns ("pdf", "report.pdf")
  3. For "data.csv", it returns ("csv", "data.csv")
  4. Python sorts by the first element of the tuple (extension), then by the second element (full filename)
  5. This groups files by extension and sorts alphabetically within each group

Why use a helper function?

Compare the readability:

python
# Without helper function - harder to understand
sorted_files = sorted(files, key=lambda f: (f.split(".")[-1], f))
 
# With helper function - clearer intent
sorted_files = sorted(files, key=lambda f: (get_extension(f), f))

The helper function makes your code self-documenting. Anyone reading get_extension(f) immediately understands what's happening, whereas f.split(".")[-1] requires mental parsing.

37.5.8) sorted() vs .sort(): When to Use Each

Python provides two ways to sort:

  1. sorted() - Function that returns a new sorted list
  2. .sort() - List method that sorts in place
python
# sorted() - creates new list, original unchanged
numbers = [3, 1, 4, 1, 5]
sorted_numbers = sorted(numbers)
print(f"Original: {numbers}")        # Output: Original: [3, 1, 4, 1, 5]
print(f"Sorted: {sorted_numbers}")   # Output: Sorted: [1, 1, 3, 4, 5]
 
# .sort() - modifies list in place, returns None
numbers = [3, 1, 4, 1, 5]
result = numbers.sort()
print(f"Modified: {numbers}")        # Output: Modified: [1, 1, 3, 4, 5]
print(f"Return value: {result}")     # Output: Return value: None

When to use sorted():

  • You need to keep the original order
  • You're sorting something other than a list (tuple, string, set, etc.)
  • You want to sort and assign in one expression

When to use .sort():

  • You have a list and don't need the original order
  • You want to save memory (no new list created)
  • You're sorting a large list in place for efficiency

The sorted() function is one of Python's most versatile tools. Combined with the key parameter, it can handle virtually any sorting requirement, from simple number ordering to complex multi-criteria sorting of nested data structures.


This chapter has equipped you with Python's essential built-in functions and tools. You've learned how to:

  • Understand Python's type hierarchy and predict which operations work with which types
  • Use fundamental functions like len(), sum(), min(), max(), abs(), and round() for common operations
  • Iterate with position information using enumerate()
  • Process parallel sequences simultaneously with zip()
  • Make decisions across collections using any() and all()
  • Sort data flexibly with sorted() and custom key functions

These tools form the foundation of idiomatic Python code. They're efficient, readable, and handle edge cases correctly. As you continue programming, you'll find yourself reaching for these functions constantly—they're the building blocks that make Python code elegant and expressive.

In the next chapter, we'll explore decorators, which allow you to modify and enhance function behavior in powerful ways, building on the first-class function concepts we learned in Chapter 23.

© 2025. Primesoft Co., Ltd.
support@primesoft.ai