Dekoratory z parametrami – gra Fizz Buzz

Gra Fizz Buzz to klasyczne zadanie – test na odfiltrowanie z ciągu liczb tych, które są podzielne przez zadane liczby bez reszty tzn. jeśli mamy np. ciąg od 0 do 5 i szukamy takich liczb, które dzielą się np. przez 3 i przez 5 bez reszty, to kolejno otrzymujemy: dla 0 łańcuch FIZZBUZZ, dla 1 pusty łańcuch, dla 2 pusty łańcuch, dla 3 otrzymujemy FIZZ, dla 4 mamy pusty łańcuch i dla 5 mamy BUZZ.

Najprościej możemy to rozwiązać w następujący sposób:

def fizzbuzz(start, end, fizz, buzz):
    """ Sample fizzbuzz game"""
    for number in range(start, end + 1):
        if not number % fizz and not number % buzz:
            print(number, 'FIZZBUZZ')
            continue
        elif not number % fizz:
            print(number, 'FIZZ')
            continue
        elif not number % buzz:
            print(number, 'BUZZ')
            continue
        print(number)

Moje rozwiązanie zadania za pomocą dekoratorów wygląda następująco:

def fizz(fizz_number):
    def decor(func):
        def wrapper(*args):
            dict = func(*args)
            for key in dict:
                if not (key % fizz_number):
                    dict[key] += 'FIZZ'
            return dict
        return wrapper
    return decor

Analogicznie funkcja buzz() wygląda następująco:

def buzz(buzz_number):
    def decor(func):
        def wrapper(*args):
            dict = func(*args)
            for key in dict:
                if not (key % buzz_number):
                    dict[key] += 'BUZZ'
            return dict
        return wrapper
    return decor

Utworzę jeszcze funkcję numbers(), która będzie zwracała słownik o kluczach będących liczbami o zakresie podanym jako argumenty tzn.

def numbers(start, end):
    dict = {}
    for number in range(start, end + 1):
        dict[number] = ''
    return dict

Powyższą funkcję możemy zmodyfikować za pomocą utworzonych wcześniej dekoratorów z parametrami tzn.

@buzz(5)
@fizz(3)
def numbers(start, end):
    dict = {}
    for number in range(start, end + 1):
        dict[number] = ''
    return dict

Zmodyfikowany słownik możemy wyświetlić za pomocą wywołania zmodyfikowanej poprzez użycie dekoratorów funkcji numbers() tzn.

print(numbers(0, 15))

Można zauważyć, że obie funkcje dekoratora są bardzo podobne, więc aby uniknąć redundancji kodu możemy obie funkcje zastąpić jedną:

def fizzbuzz(number, message):
    def decor(func):
        def wrapper(*args):
            dict = func(*args)
            for key in dict:
                if not (key % number):
                    dict[key] += message
            return dict
        return wrapper
    return decor

Definicja funkcji numbers() po dodaniu dekoratora będzie wyglądać następująco:

@fizzbuzz(5, 'BUZZ')
@fizzbuzz(3, 'FIZZ')
def numbers(start, end):
    dict = {}
    for number in range(start, end + 1):
        dict[number] = ''
    return dict