class tracer:                                # Stan w atrybutach instancji
    def __init__(self, func):                # W momencie dekoracji @
        self.calls = 0                       # Zapisanie func dla późniejszego wywołania
        self.func  = func
    def __call__(self, *args, **kwargs):     # W momencie wywołania oryginalnej funkcji
        self.calls += 1
        print(f'wywołanie {self.calls} to {self.func.__name__}')
        return self.func(*args, **kwargs)

@tracer
def hack(a, b, c):           # To samo co: hack = tracer(hack)
    print(a + b + c)         # Uruchamia tracer.__init__

@tracer
def code(x, y):              # To samo co: code = tracer(code)
    print(x ** y)            # Opakowuje code w obiekt tracer

if __name__ == '__main__':
    hack(1, 2, 3)            # Tak naprawdę wywołuje instancję tracer: wykonuje tracer.__call__
    hack(a=4, b=5, c=6)      # hack jest atrybutem instancji

    code(4, 2)               # Tak naprawdę wywołuje instancję tracer, self.func to code
    code(2, y=16)            # self.calls zliczane tutaj per funkcja (potrzebna instrukcja nonlocal z 3.0)

