class tracer(object):                        # Dekorator i deskryptor
    def __init__(self, func):                # W momencie dekoracji @
        self.calls = 0                       # Zapisanie func na potrzeby 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)
    def __get__(self, instance, owner):      # W momencie pobrania atrybutu metody
        return wrapper(self, instance)

class wrapper:
    def __init__(self, desc, subj):          # Zapisanie obu instancji
        self.desc = desc                     # Przekierowanie wywołań z powrotem do dekoratora
        self.subj = subj
    def __call__(self, *args, **kwargs):
        return self.desc(self.subj, *args, **kwargs)  # Wykonuje tracer.__call__


@tracer
def hack(a, b, c):                           # hack = tracer(hack)
    print(a + b + c)                         # Wykorzystuje jedynie __call__

class Person:
    def __init__(self, name, pay):
        self.name = name
        self.pay  = pay
    @tracer                                  # giveRaise = tracer(giverRaise)
    def giveRaise(self, percent):            # Sprawia, że giveRaise staje się deskryptorem
        self.pay *= (1.0 + percent)

