Practice Questions — Decorators and Iterators
← Back to NotesTopic-Specific Questions
Question 1
Easy
What is the output of the following code?
it = iter([10, 20, 30])
print(next(it))
print(next(it))iter() creates an iterator. next() gets the next value.
1020Question 2
Easy
What is the output?
it = iter("abc")
print(next(it))
print(next(it))
print(next(it))Iterating over a string yields one character at a time.
abcQuestion 3
Easy
What is the output?
from itertools import chain
result = list(chain([1, 2], [3, 4]))
print(result)chain concatenates multiple iterables.
[1, 2, 3, 4]Question 4
Easy
What is the output?
def shout(func):
def wrapper():
return func().upper()
return wrapper
@shout
def greet():
return "hello"
print(greet())The decorator wraps greet and calls .upper() on its result.
HELLOQuestion 5
Easy
What is the output?
my_list = [1, 2, 3]
print(hasattr(my_list, '__iter__'))
print(hasattr(my_list, '__next__'))Lists are iterable but not iterators.
TrueFalseQuestion 6
Medium
What is the output?
from itertools import combinations
result = list(combinations([1, 2, 3], 2))
print(result)
print(len(result))combinations(n, r) gives all subsets of size r.
[(1, 2), (1, 3), (2, 3)]3Question 7
Medium
What is the output?
from itertools import permutations
result = list(permutations("ab", 2))
print(result)
print(len(result))permutations considers order, so (a,b) and (b,a) are different.
[('a', 'b'), ('b', 'a')]2Question 8
Medium
What is the output?
class Counter:
def __init__(self, limit):
self.limit = limit
self.current = 0
def __iter__(self):
return self
def __next__(self):
if self.current >= self.limit:
raise StopIteration
self.current += 1
return self.current
print(list(Counter(4)))The iterator yields 1, 2, 3, 4 then raises StopIteration.
[1, 2, 3, 4]Question 9
Medium
What is the output?
def double_result(func):
def wrapper(*args, **kwargs):
return func(*args, **kwargs) * 2
return wrapper
@double_result
def add(a, b):
return a + b
print(add(3, 4))
print(add.__name__)The decorator doubles the return value. No @wraps is used.
14wrapperQuestion 10
Medium
What is the output?
from functools import wraps
def bold(func):
@wraps(func)
def wrapper(*args, **kwargs):
return f"{func(*args, **kwargs)}"
return wrapper
def italic(func):
@wraps(func)
def wrapper(*args, **kwargs):
return f"{func(*args, **kwargs)}"
return wrapper
@bold
@italic
def greet(name):
return name
print(greet("Aarav"))Decorators apply bottom to top. @italic wraps first, @bold wraps second.
<b><i>Aarav</i></b>Question 11
Hard
What is the output?
from itertools import islice, count
def squares():
n = 1
while True:
yield n * n
n += 1
result = list(islice(squares(), 5))
print(result)
result2 = list(islice(count(10, 5), 4))
print(result2)islice takes the first N values from any iterator. count(10, 5) counts 10, 15, 20, ...
[1, 4, 9, 16, 25][10, 15, 20, 25]Question 12
Hard
What is the output?
from itertools import product
result = list(product([0, 1], repeat=3))
print(len(result))
print(result[0])
print(result[-1])product with repeat=3 gives all 3-length combinations of [0,1], like binary numbers.
8(0, 0, 0)(1, 1, 1)Question 13
Hard
What is the output?
def repeat(n):
def decorator(func):
def wrapper(*args, **kwargs):
return [func(*args, **kwargs) for _ in range(n)]
return wrapper
return decorator
@repeat(3)
def greet(name):
return f"Hi, {name}"
print(greet("Aarav"))repeat(3) is a decorator factory. The inner decorator wraps greet to call it 3 times.
['Hi, Aarav', 'Hi, Aarav', 'Hi, Aarav']Question 14
Hard
What is the output?
class Doubler:
def __init__(self, func):
self.func = func
def __call__(self, *args, **kwargs):
return self.func(*args, **kwargs) * 2
@Doubler
def add(a, b):
return a + b
print(add(3, 4))
print(type(add).__name__)Doubler is a class-based decorator. add is now an instance of Doubler.
14DoublerQuestion 15
Hard
What is the output?
it = iter([10, 20, 30])
print(next(it)) # 10
print(next(it)) # 20
for val in it:
print(val, end=" ")
print()
for val in it:
print(val, end=" ")
print("(empty)")The for loop continues from where next() left off. After exhaustion, the second for loop gets nothing.
102030 (empty)Question 16
Medium
What is the difference between an iterable and an iterator?
Think about which methods each one must have.
An iterable is any object that has an
__iter__() method, which returns an iterator. Lists, strings, tuples, dicts, sets, and ranges are all iterables. An iterator is an object that has both __iter__() and __next__() methods. Calling iter() on an iterable returns an iterator. Calling next() on an iterator returns the next value.Question 17
Hard
Why should you always use
@functools.wraps(func) in your decorators?Think about what happens to the function's name and docstring.
Without
@wraps(func), the decorated function loses its original __name__, __doc__, __module__, and other metadata. Instead, these are replaced with the wrapper function's metadata. This breaks debugging (stack traces show 'wrapper' instead of the real function name), documentation (help() shows nothing useful), and introspection tools. @wraps(func) copies the original function's metadata to the wrapper.Mixed & Application Questions
Question 1
Easy
What is the output?
it = iter(range(3))
print(list(it))
print(list(it))An iterator can only be consumed once.
[0, 1, 2][]Question 2
Easy
What is the output?
from itertools import cycle
c = cycle(["A", "B"])
result = [next(c) for _ in range(5)]
print(result)cycle repeats the iterable endlessly.
['A', 'B', 'A', 'B', 'A']Question 3
Medium
What is the output?
def prefix(pre):
def decorator(func):
def wrapper(*args, **kwargs):
return f"{pre}: {func(*args, **kwargs)}"
return wrapper
return decorator
@prefix("ERROR")
def message(text):
return text
@prefix("INFO")
def info(text):
return text
print(message("file not found"))
print(info("server started"))prefix is a decorator factory. It creates decorators that prepend a string.
ERROR: file not foundINFO: server startedQuestion 4
Medium
What is the output?
from itertools import islice
def naturals():
n = 1
while True:
yield n
n += 1
def evens(numbers):
for n in numbers:
if n % 2 == 0:
yield n
result = list(islice(evens(naturals()), 5))
print(result)naturals yields 1, 2, 3, ... evens filters to even numbers. islice takes first 5.
[2, 4, 6, 8, 10]Question 5
Medium
What is the output?
class Squares:
def __init__(self, n):
self.n = n
def __iter__(self):
for i in range(1, self.n + 1):
yield i ** 2
print(list(Squares(5)))
print(sum(Squares(3)))__iter__ can be a generator function (using yield). Each call to iter() creates a fresh generator.
[1, 4, 9, 16, 25]14Question 6
Medium
What is the output?
d = {"a": 1, "b": 2, "c": 3}
it = iter(d)
print(next(it))
print(next(it))
print(list(it))Iterating over a dict yields its keys.
ab['c']Question 7
Hard
What is the output?
def validate_positive(func):
def wrapper(*args):
for arg in args:
if arg < 0:
raise ValueError(f"Negative value: {arg}")
return func(*args)
return wrapper
@validate_positive
def add(a, b):
return a + b
print(add(3, 5))
try:
print(add(-1, 5))
except ValueError as e:
print(f"Error: {e}")The decorator checks all arguments before calling the function.
8Error: Negative value: -1Question 8
Hard
What is the output?
from itertools import product, combinations
# How many ways to pick 2 from 4 students?
students = ["Aarav", "Priya", "Rohan", "Meera"]
pairs = list(combinations(students, 2))
print(f"Pairs: {len(pairs)}")
# How many 2-character strings from 'AB'?
codes = list(product("AB", repeat=2))
print(f"Codes: {len(codes)}")
print(f"First: {''.join(codes[0])}")
print(f"Last: {''.join(codes[-1])}")C(4,2) = 6 combinations. 2^2 = 4 products.
Pairs: 6Codes: 4First: AALast: BBQuestion 9
Hard
What is the output?
from functools import wraps
def debug(func):
@wraps(func)
def wrapper(*args, **kwargs):
args_str = ", ".join(repr(a) for a in args)
print(f" {func.__name__}({args_str})")
result = func(*args, **kwargs)
print(f" -> {result}")
return result
return wrapper
@debug
def factorial(n):
if n <= 1:
return 1
return n * factorial(n - 1)
print(f"Result: {factorial(4)}")The decorated factorial calls itself, so debug prints for every recursive call.
factorial(4) factorial(3) factorial(2) factorial(1) -> 1 -> 2 -> 6 -> 24Result: 24Question 10
Hard
What is the output?
class FibIterator:
def __init__(self, limit):
self.limit = limit
self.a, self.b = 0, 1
def __iter__(self):
return self
def __next__(self):
if self.a > self.limit:
raise StopIteration
value = self.a
self.a, self.b = self.b, self.a + self.b
return value
print(list(FibIterator(20)))
print(sum(FibIterator(10)))The iterator yields Fibonacci numbers up to the limit.
[0, 1, 1, 2, 3, 5, 8, 13]20Question 11
Hard
How do decorators with arguments (like
@repeat(3)) differ from simple decorators (like @timer)?Think about how many levels of function nesting each requires.
A simple decorator is a function that takes a function and returns a wrapper:
def timer(func): def wrapper(...): ... return wrapper. A decorator with arguments needs an extra layer: def repeat(n): def decorator(func): def wrapper(...): ... return wrapper return decorator. The outer function (repeat(3)) returns the actual decorator. So @repeat(3) first calls repeat(3) which returns a decorator function, and then that decorator is applied to the function below.Multiple Choice Questions
MCQ 1
What two methods must an iterator implement?
Answer: B
B is correct. The iterator protocol requires
B is correct. The iterator protocol requires
__iter__() (returns the iterator) and __next__() (returns the next value or raises StopIteration).MCQ 2
What does the @ symbol do before a function definition?
Answer: B
B is correct.
B is correct.
@decorator above a function is syntactic sugar for func = decorator(func). It applies the decorator, which wraps or modifies the function.MCQ 3
What exception is raised when an iterator has no more values?
Answer: C
C is correct. When an iterator's
C is correct. When an iterator's
__next__() method has no more values, it raises StopIteration. A for loop catches this automatically to end the loop.MCQ 4
What does iter([1, 2, 3]) return?
Answer: B
B is correct.
B is correct.
iter() called on a list returns a list_iterator object. This iterator has __next__() and produces values one at a time.MCQ 5
What does a decorator function return?
Answer: C
C is correct. A decorator takes a function as input and returns a new function (the wrapper) that typically calls the original function with added behavior. The wrapper replaces the original function.
C is correct. A decorator takes a function as input and returns a new function (the wrapper) that typically calls the original function with added behavior. The wrapper replaces the original function.
MCQ 6
What does itertools.chain do?
Answer: B
B is correct.
B is correct.
chain(iter1, iter2, ...) produces all elements from iter1, then all from iter2, etc. It concatenates iterables into a single sequence.MCQ 7
What is functools.wraps used for?
Answer: C
C is correct.
C is correct.
@wraps(func) copies the original function's __name__, __doc__, and other metadata to the wrapper function. Without it, the decorated function appears to be named 'wrapper'.MCQ 8
How are stacked decorators applied?
Answer: B
B is correct.
B is correct.
@A @B def f means f = A(B(f)). The bottom decorator (B) wraps f first, then the top decorator (A) wraps the result. When called, A's wrapper runs first (outermost), then B's.MCQ 9
What is the difference between permutations and combinations?
Answer: B
B is correct. Permutations treat (A,B) and (B,A) as different. Combinations treat them as the same. For n items taken r at a time, there are more permutations than combinations.
B is correct. Permutations treat (A,B) and (B,A) as different. Combinations treat them as the same. For n items taken r at a time, there are more permutations than combinations.
MCQ 10
Can a list be passed directly to next()?
Answer: B
B is correct.
B is correct.
next() requires an iterator (an object with __next__()). Lists do not have __next__(). You must call iter(my_list) first to get a list_iterator, then call next() on that.MCQ 11
What does itertools.product(['A','B'], [1,2]) produce?
Answer: B
B is correct.
B is correct.
product creates the Cartesian product: every element from the first iterable paired with every element from the second. Result: [('A',1), ('A',2), ('B',1), ('B',2)].MCQ 12
A class-based decorator must implement which method to be callable?
Answer: B
B is correct. A class-based decorator uses
B is correct. A class-based decorator uses
__call__ to make instances callable. When the decorated function is called, Python invokes instance.__call__(*args, **kwargs).MCQ 13
What is the extra nesting level needed for in decorators with arguments?
Answer: B
B is correct.
B is correct.
@repeat(3) first calls repeat(3), which returns the actual decorator function. That decorator then receives the function. Three levels: outer (args) -> middle (func) -> inner (wrapper).MCQ 14
What does the @property decorator do?
Answer: B
B is correct.
B is correct.
@property allows a method to be accessed like an attribute (without parentheses). The method is called automatically when the attribute is accessed. A companion @name.setter decorator can define assignment behavior.MCQ 15
What is itertools.islice used for?
Answer: B
B is correct.
B is correct.
islice(iterable, start, stop, step) works like regular slicing but on any iterator, including generators and infinite iterators. It does not create intermediate lists, making it memory-efficient.MCQ 16
What does itertools.count(5, 2) produce?
Answer: A
A is correct.
A is correct.
count(5, 2) produces an infinite sequence starting at 5, incrementing by 2: 5, 7, 9, 11, 13, ... Use islice to take a finite number of values.MCQ 17
If __iter__ returns a fresh iterator each time, what advantage does this provide?
Answer: A
A is correct. If
A is correct. If
__iter__ returns a new iterator each time (like a list does), the object can be iterated multiple times with for loops. If __iter__ returns self (like a generator does), the object can only be iterated once.MCQ 18
What is the output of combinations('ABCD', 2) in terms of count?
Answer: B
B is correct. C(4,2) = 4!/(2! * 2!) = 6. The six combinations are: AB, AC, AD, BC, BD, CD. Combinations do not consider order, so AB and BA count as one.
B is correct. C(4,2) = 4!/(2! * 2!) = 6. The six combinations are: AB, AC, AD, BC, BD, CD. Combinations do not consider order, so AB and BA count as one.
Coding Challenges
Challenge 1: Custom Range Iterator
EasyCreate a class EvenNumbers that implements the iterator protocol and yields even numbers from start to stop (inclusive if even). Test it with a for loop, list(), and next().
Sample Input
(No input required)
Sample Output
For loop: 2 4 6 8 10
As list: [0, 2, 4, 6, 8]
next: 4, 6, 8
Implement __iter__ and __next__. Raise StopIteration when done.
class EvenNumbers:
def __init__(self, start, stop):
self.stop = stop
self.current = start if start % 2 == 0 else start + 1
def __iter__(self):
return self
def __next__(self):
if self.current > self.stop:
raise StopIteration
value = self.current
self.current += 2
return value
print("For loop:", end=" ")
for n in EvenNumbers(1, 10):
print(n, end=" ")
print()
print(f"As list: {list(EvenNumbers(0, 9))}")
it = EvenNumbers(4, 9)
print(f"next: {next(it)}, {next(it)}, {next(it)}")Challenge 2: Simple Logger Decorator
EasyWrite a decorator called logger that prints the function name, arguments, and return value every time the decorated function is called. Use functools.wraps to preserve metadata.
Sample Input
(No input required)
Sample Output
Calling add(3, 5)
add returned 8
Result: 8
add.__name__: add
Use *args and **kwargs. Use functools.wraps.
from functools import wraps
def logger(func):
@wraps(func)
def wrapper(*args, **kwargs):
args_str = ", ".join([repr(a) for a in args] + [f"{k}={v!r}" for k, v in kwargs.items()])
print(f"Calling {func.__name__}({args_str})")
result = func(*args, **kwargs)
print(f"{func.__name__} returned {result}")
return result
return wrapper
@logger
def add(a, b):
"""Add two numbers."""
return a + b
result = add(3, 5)
print(f"Result: {result}")
print(f"add.__name__: {add.__name__}")
print(f"add.__doc__: {add.__doc__}")Challenge 3: itertools Practice Problems
EasyUsing itertools, solve these: (1) generate all 2-letter combinations from 'ABCD', (2) create all possible 3-digit binary numbers using product, (3) take the first 8 values from an infinite counter starting at 100 with step 5, (4) chain three lists into one.
Sample Input
(No input required)
Sample Output
2-letter combos: 6 total
Binary 3-digit: 8 total, first: (0, 0, 0)
Counter: [100, 105, 110, 115, 120, 125, 130, 135]
Chained: [1, 2, 3, 4, 5, 6]
Use combinations, product, count with islice, and chain.
from itertools import combinations, product, count, islice, chain
# 1. All 2-letter combinations from ABCD
combos = list(combinations('ABCD', 2))
print(f"2-letter combos: {len(combos)} total")
# 2. All 3-digit binary numbers
binary = list(product([0, 1], repeat=3))
print(f"Binary 3-digit: {len(binary)} total, first: {binary[0]}")
# 3. First 8 from counter starting at 100, step 5
counter_vals = list(islice(count(100, 5), 8))
print(f"Counter: {counter_vals}")
# 4. Chain three lists
result = list(chain([1, 2], [3, 4], [5, 6]))
print(f"Chained: {result}")Challenge 4: Memoize Decorator
MediumWrite a memoize decorator that caches function results based on arguments. Add a .cache attribute to inspect cached values and a .clear_cache() method to reset it. Test with a recursive fibonacci function.
Sample Input
(No input required)
Sample Output
fib(10) = 55
fib(20) = 6765
Cache size: 21
After clear: 0
Use a dictionary for the cache. Use functools.wraps. Attach cache and clear_cache to the wrapper.
from functools import wraps
def memoize(func):
cache = {}
@wraps(func)
def wrapper(*args):
if args not in cache:
cache[args] = func(*args)
return cache[args]
wrapper.cache = cache
wrapper.clear_cache = lambda: cache.clear()
return wrapper
@memoize
def fib(n):
if n < 2:
return n
return fib(n - 1) + fib(n - 2)
print(f"fib(10) = {fib(10)}")
print(f"fib(20) = {fib(20)}")
print(f"Cache size: {len(fib.cache)}")
fib.clear_cache()
print(f"After clear: {len(fib.cache)}")Challenge 5: Deck of Cards Iterator
MediumCreate a Deck class that implements the iterator protocol to yield all 52 playing cards. Each card should be a tuple of (rank, suit). Support shuffling with a shuffle() method and iterating with for loops.
Sample Input
(No input required)
Sample Output
Total cards: 52
First 3: [('2', 'Hearts'), ('3', 'Hearts'), ('4', 'Hearts')]
After shuffle (first 3): [('K', 'Diamonds'), ('7', 'Clubs'), ('3', 'Hearts')]
Use itertools.product to generate all cards. Implement __iter__ and __len__.
import random
from itertools import product
class Deck:
RANKS = ['2', '3', '4', '5', '6', '7', '8', '9', '10', 'J', 'Q', 'K', 'A']
SUITS = ['Hearts', 'Diamonds', 'Clubs', 'Spades']
def __init__(self):
self.cards = [(r, s) for r, s in product(self.RANKS, self.SUITS)]
def __iter__(self):
return iter(self.cards)
def __len__(self):
return len(self.cards)
def shuffle(self):
random.shuffle(self.cards)
return self
deck = Deck()
print(f"Total cards: {len(deck)}")
first_3 = [card for _, card in zip(range(3), deck)]
print(f"First 3: {first_3}")
random.seed(42)
deck.shuffle()
first_3_shuffled = [card for _, card in zip(range(3), deck)]
print(f"After shuffle (first 3): {first_3_shuffled}")Challenge 6: Access Control Decorator
MediumWrite a decorator requires_role(role) that checks if the current user (passed as the first argument with a .role attribute) has the required role. If not, raise a PermissionError. Test with admin and viewer roles.
Sample Input
(No input required)
Sample Output
Admin panel: Welcome, Aarav
PermissionError: Priya requires 'admin' role, has 'viewer'
The decorator should take a role argument. Use functools.wraps.
from functools import wraps
def requires_role(role):
def decorator(func):
@wraps(func)
def wrapper(user, *args, **kwargs):
if user.role != role:
raise PermissionError(
f"{user.name} requires '{role}' role, has '{user.role}'"
)
return func(user, *args, **kwargs)
return wrapper
return decorator
class User:
def __init__(self, name, role):
self.name = name
self.role = role
@requires_role('admin')
def admin_panel(user):
return f"Welcome, {user.name}"
@requires_role('admin')
def delete_user(user, target):
return f"{user.name} deleted {target}"
admin = User("Aarav", "admin")
viewer = User("Priya", "viewer")
print(f"Admin panel: {admin_panel(admin)}")
try:
admin_panel(viewer)
except PermissionError as e:
print(f"PermissionError: {e}")Challenge 7: Infinite Iterator Toolkit
HardCreate custom iterators (without using itertools): (1) InfiniteCounter(start, step) that counts forever, (2) Cycle(iterable) that repeats endlessly, (3) TakeWhile(predicate, iterator) that yields while the predicate is True, (4) Chain(*iterables) that concatenates iterables. Implement all using the iterator protocol.
Sample Input
(No input required)
Sample Output
Counter: [5, 8, 11, 14, 17]
Cycle: ['a', 'b', 'c', 'a', 'b']
TakeWhile: [1, 3, 5]
Chain: [1, 2, 3, 4, 5, 6]
Implement __iter__ and __next__ for each class. Do not use itertools.
class InfiniteCounter:
def __init__(self, start=0, step=1):
self.current = start
self.step = step
def __iter__(self):
return self
def __next__(self):
value = self.current
self.current += self.step
return value
class Cycle:
def __init__(self, iterable):
self.items = list(iterable)
self.index = 0
def __iter__(self):
return self
def __next__(self):
if not self.items:
raise StopIteration
value = self.items[self.index]
self.index = (self.index + 1) % len(self.items)
return value
class TakeWhile:
def __init__(self, predicate, iterator):
self.predicate = predicate
self.iterator = iter(iterator)
self.done = False
def __iter__(self):
return self
def __next__(self):
if self.done:
raise StopIteration
value = next(self.iterator)
if not self.predicate(value):
self.done = True
raise StopIteration
return value
class Chain:
def __init__(self, *iterables):
self.iterables = iter(iterables)
self.current = iter([])
def __iter__(self):
return self
def __next__(self):
while True:
try:
return next(self.current)
except StopIteration:
self.current = iter(next(self.iterables))
# Test
counter = InfiniteCounter(5, 3)
print(f"Counter: {[next(counter) for _ in range(5)]}")
cycler = Cycle(['a', 'b', 'c'])
print(f"Cycle: {[next(cycler) for _ in range(5)]}")
tw = TakeWhile(lambda x: x < 7, [1, 3, 5, 7, 9, 2])
print(f"TakeWhile: {list(tw)}")
ch = Chain([1, 2], [3, 4], [5, 6])
print(f"Chain: {list(ch)}")Challenge 8: Decorator Collection: Timer, Retry, and Type Check
HardWrite three advanced decorators: (1) timer that measures and prints execution time, (2) retry(max_attempts, exceptions) that retries on specified exceptions, (3) type_check(**expected_types) that validates argument types before calling the function. All should use functools.wraps.
Sample Input
(No input required)
Sample Output
slow_func took 0.1001s
Attempt 1 failed. Retrying...
Attempt 2 succeeded.
add(3, 5) = 8
TypeError: Expected b to be <class 'int'>, got <class 'str'>
All decorators must use functools.wraps. retry should re-raise on final failure. type_check should inspect function parameter names.
import time
from functools import wraps
import inspect
def timer(func):
@wraps(func)
def wrapper(*args, **kwargs):
start = time.perf_counter()
result = func(*args, **kwargs)
elapsed = time.perf_counter() - start
print(f"{func.__name__} took {elapsed:.4f}s")
return result
return wrapper
def retry(max_attempts=3, exceptions=(Exception,)):
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
for attempt in range(1, max_attempts + 1):
try:
result = func(*args, **kwargs)
print(f"Attempt {attempt} succeeded.")
return result
except exceptions as e:
print(f"Attempt {attempt} failed. Retrying...")
if attempt == max_attempts:
raise
return wrapper
return decorator
def type_check(**expected_types):
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
sig = inspect.signature(func)
params = list(sig.parameters.keys())
for i, arg in enumerate(args):
if i < len(params) and params[i] in expected_types:
if not isinstance(arg, expected_types[params[i]]):
raise TypeError(
f"Expected {params[i]} to be {expected_types[params[i]]}, "
f"got {type(arg)}"
)
return func(*args, **kwargs)
return wrapper
return decorator
@timer
def slow_func():
time.sleep(0.1)
return "done"
slow_func()
call_count = 0
@retry(max_attempts=3, exceptions=(ValueError,))
def flaky():
global call_count
call_count += 1
if call_count < 2:
raise ValueError("random failure")
return "success"
flaky()
@type_check(a=int, b=int)
def add(a, b):
return a + b
print(f"add(3, 5) = {add(3, 5)}")
try:
add(3, "5")
except TypeError as e:
print(f"TypeError: {e}")Need to Review the Concepts?
Go back to the detailed notes for this chapter.
Read Chapter NotesWant to learn Python with a live mentor?
Explore our Python course