Objects That Yield Items One at a Time
In Python, an iterable is an object that can produce one item at a time for a loop or another consumer. Strings, lists, tuples, sets, dictionaries, files, and generators are all iterable even though they organize and produce items differently.
An iterable does not have to be ordered, indexable, reusable, or finite. Calling iter() must return an iterator that yields items. A finite iterator stops when it is exhausted.
Types of Iterables
Strings, lists, and tuples yield one item per iteration. The examples below show a character, a number, or a coordinate being passed to the loop variable.
# String as iterable
text = "hello"
print("Characters in string:")
for char in text:
print(char)
# Output:
# Characters in string:
# h
# e
# l
# l
# o
print()
# List as iterable
numbers = [1, 2, 3, 4, 5]
print("Elements in list:")
for num in numbers:
print(num)
# Output:
# Elements in list:
# 1
# 2
# 3
# 4
# 5
print()
# Tuple as iterable
coordinates = (10, 20, 30)
print("Elements in tuple:")
for coord in coordinates:
print(coord)
# Output:
# Elements in tuple:
# 10
# 20
# 30The string yields characters in text order. Lists and tuples yield their stored elements in position order. Other iterable types may follow different ordering rules.
A list can be traversed again, but a generator object remembers its iteration progress. The first list(iterator) exhausts the generator below. The second call does not restart the computation, so it returns an empty list.
numbers = [1, 2, 3]
iterator = (number * 2 for number in numbers)
print(list(iterator)) # Output: [2, 4, 6]
print(list(iterator)) # Output: []
print(list(numbers)) # Output: [1, 2, 3]Membership Operators
The operators in and not in ask whether a container contains a value. Each container type defines how membership is checked: a list scans its elements, a dictionary looks up its keys, and a string looks for a substring.
Using in and not in Operators
Lists and tuples compare elements until a match or the end. Strings search for a substring. Sets and dictionaries use their own lookup behavior. In a dictionary, in checks keys, not values.
# Membership checking in list
fruits = ["apple", "orange", "mango"]
# Using in operator
if "apple" in fruits:
print("Apple is available in the fruit list")
# Output: Apple is available in the fruit list
# Using not in operator
if "durian" not in fruits:
print("Durian is not available in the fruit list")
# Output: Durian is not available in the fruit list
print()
# Membership checking in string
message = "Python is a programming language"
# Searching substring in string
if "Python" in message:
print("Python word found in message")
# Output: Python word found in message
if "Java" not in message:
print("Java word not found in message")
# Output: Java word not found in messageSpecial Cases in Strings
For strings, the left operand may be a single character or a longer substring. The test is case-sensitive and checks for one contiguous occurrence.
# Character checking in string
text = "programming"
# Searching single character
print("g" in text) # Output: True
print("z" in text) # Output: False
print()
# Searching substring in string
print("gram" in text) # Output: True
print("program" in text) # Output: True
print("java" in text) # Output: False
# Check for the marker used in this small example
email = "user@example.com"
if "@" in email and ".com" in email:
print("Contains basic email markers")
# Output: Contains basic email markersThe final check only demonstrates two markers. It is not sufficient email validation because many invalid addresses contain both @ and .com, while many valid addresses use another domain suffix.
Any and All Functions
The built-ins any() and all() consume an iterable and test the truthiness of its items. Both stop as soon as the result is known, so generator expressions avoid building an unnecessary intermediate list.
The Any Function
any() returns True after the first truthy item. It returns False only when every item is falsy or the iterable is empty.
# Example using any()
grades = [60, 75, 45, 80]
# Check if there are values above 70
print("High grades exist:", any(grade > 70 for grade in grades))
# Output: High grades exist: True
# Example with direct boolean
conditions = [False, False, True, False]
print("True condition exists:", any(conditions))
# Output: True condition exists: True
# Example with empty list
empty_list = []
print("Any on empty list:", any(empty_list))
# Output: Any on empty list: False
# Practical example
numbers = [0, 0, 5, 0]
if any(numbers):
print("Non-zero numbers exist in list")
# Output: Non-zero numbers exist in listThe All Function
all() returns False after the first falsy item. If iteration ends without one, it returns True. This also explains the empty case: there is no item that violates the condition.
# Example using all()
scores = [85, 90, 78, 92]
# Check if all values are above 70
print("All grades pass:", all(score >= 70 for score in scores))
# Output: All grades pass: True
# Example with direct boolean
conditions = [True, True, True, True]
print("All conditions true:", all(conditions))
# Output: All conditions true: True
# Example with one false condition
mixed_conditions = [True, True, False, True]
print("All conditions true:", all(mixed_conditions))
# Output: All conditions true: False
# Practical example
ages = [18, 21, 25, 30]
if all(age >= 18 for age in ages):
print("All participants are adults")
# Output: All participants are adults
print(all([])) # Output: TrueUnpacking Operator
In a function call, a leading asterisk (*) expands an iterable into positional arguments. The resulting argument count must still match what the function accepts.
Problems Without Unpacking
A function expecting separate coordinates receives a list as one argument unless you unpack it. Manual indexing works for a fixed shape but repeats the container structure at every call site.
import math
# Problem: math.hypot function needs separate arguments
coordinates = [3, 4]
# Wrong way - will result in error
try:
result = math.hypot(coordinates)
print(result)
except TypeError as e:
print("Error:", e)
# Output: Error: must be real number, not list
# Manual way that's troublesome for many arguments
result = math.hypot(coordinates[0], coordinates[1])
print("Manual result:", result)
# Output: Manual result: 5.0
# Example with more arguments
points = [1, 1, 1, 1]
# Manual way becomes very troublesome
result = math.hypot(points[0], points[1], points[2], points[3])
print("Result with 4 arguments:", result)
# Output: Result with 4 arguments: 2.0Solution with Unpacking Operator
Placing * before the iterable in the call passes each produced item as its own positional argument.
import math
# Solution with unpacking operator
coordinates = [3, 4]
result = math.hypot(*coordinates)
print("Distance from origin:", result)
# Output: Distance from origin: 5.0
# Example with tuple
point_3d = (1, 2, 2)
distance = math.hypot(*point_3d)
print("3D distance:", distance)
# Output: 3D distance: 3.0
# Example with many arguments
dimensions = [2, 3, 6, 1, 4]
euclidean_distance = math.hypot(*dimensions)
print("Euclidean distance:", euclidean_distance)
# Output: Euclidean distance: 8.12403840463596
# Other practical example
def calculate_average(a, b, c):
return (a + b + c) / 3
grades = [85, 90, 78]
average = calculate_average(*grades)
print("Average grade:", average)
# Output: Average grade: 84.33333333333333Practical Uses of Unpacking
The same syntax works in function calls and in collection displays. Use it when the interface expects expanded items. Pass the iterable directly when the function accepts one iterable.
# Unpacking for print function
items = ["apple", "orange", "mango"]
print("Fruits:", *items)
# Output: Fruits: apple orange mango
# Unpacking fixed arguments for range()
bounds = [2, 10, 2]
print("Even values:", list(range(*bounds)))
# Output: Even values: [2, 4, 6, 8]
# Unpacking to combine lists
list1 = [1, 2, 3]
list2 = [4, 5, 6]
combined = [*list1, *list2]
print("Combined list:", combined)
# Output: Combined list: [1, 2, 3, 4, 5, 6]
# Unpacking in string format function
template = "Name: {}, Age: {}, City: {}"
data = ["Alice", 25, "Jakarta"]
formatted = template.format(*data)
print(formatted)
# Output: Name: Alice, Age: 25, City: Jakarta