Advanced decorators: stacking, parameters, and @property

Intermediate Data Science Praktikum
Created by Pavel · 03.04.2026 at 12:13 UTC

Once you understand the basic decorator pattern, three extensions show up everywhere.

Stacking is the first. When you write @bracket_a above @bracket_b above def f(), Python applies them bottom-up: f = bracket_a(bracket_b(f)). The bottom decorator wraps the function first; the top decorator wraps that result. Read the stack from bottom to top to trace what happens to f. In Pydantic, @field_validator('name') sits above @classmethod — the classmethod decorator fires first (making the method receive cls), then field_validator registers it.

Parametrized decorators are the second. @repeat(3) looks like a decorator with an argument, but what is really happening is that repeat(3) is called first and must return a decorator. So you need three nested functions: the outer one receives the parameter (n), the middle one receives the function (fn), and the inner one is the actual wrapper. This three-level nesting confuses everyone the first time, but once you see that repeat(3) returns a normal decorator, the pattern clicks.

@property is the third — a built-in decorator that turns a method into an attribute-like accessor. obj.radius looks like reading a field but actually calls a function. The companion @radius.setter does the same for assignment, and that is where you guard invariants: if value <= 0: raise ValueError(...). Properties make the class interface clean while keeping validation logic centralized.

Functools docs: [1], descriptor how-to (property): [2].


Sources

University approvals: 0
Tasks
Question 1

What does this code print?

def bracket_a(fn):
    def w(*a, **k): return '[ ' + fn(*a, **k) + ' ]'
    return w

def bracket_b(fn):
    def w(*a, **k): return '< ' + fn(*a, **k) + ' >'
    return w

@bracket_a
@bracket_b
def word(): return 'x'

print(word())
Hint

Bottom-up: bracket_b wraps first → '< x >'. Then bracket_a wraps that → '[ < x > ]'.

Question 2

What does this code print?

class Circle:
    def __init__(self, r):
        self._r = r

    @property
    def radius(self):
        return self._r

    @radius.setter
    def radius(self, value):
        if value <= 0:
            raise ValueError('must be positive')
        self._r = value

c = Circle(5)
c.radius = 10
print(c.radius)
Hint

10 > 0, so the setter accepts it. The getter then returns self._r.

Question 3

Implement a parametrized decorator repeat(n) that makes the decorated function run n times and returns a list of all results.

Then implement test_repeat() -> list that decorates a function and calls it.

Example: if @repeat(3) decorates def hi(): return 'hi', then hi()['hi', 'hi', 'hi'].

Submit both; tests call test_repeat().

Hint

Three levels: repeat(n) returns decorator, decorator(fn) returns wrapper, wrapper calls fn n times.

Starter code is prefilled; replace TODO blocks with your solution.
1 test case will be used for grading
Run checks runtime behavior only. Final correctness is evaluated when you submit.
Card Info
  • Topic: Data Science Praktikum
  • Difficulty: Intermediate
  • Completed: 0 users
Creator
Pavel
Pavel