I have some observations that are mostly language-independent.
Document your functions
A clear description of functionality helps when you're considering re-using code in a new project. Get in the habit of writing good doc-comments early, so you're not caught out trying to unravel your logic when it matters!
Accept command-line arguments
It's tedious to test a program if you always need to supply inputs. Simple numbers are best provided as arguments when invoking the program (consider reading from the input stream as a fallback).
Validate inputs
Does it make sense to accept negative numbers? I don't think so; we should refuse to operate if any of the inputs are zero or less.
Separate computation and I/O
It's hard to unit-test code that produces output using print()
. It's easier if we return an collection of values or if we write to a supplied iterator. Python helps us, because it allows us to write a generator simply by using yield
to return each value.
Division is the most expensive of the basic arithmetic operations
getString()
(which should be get_string()
by PEP-8, but really needs to be something more informative) has no knowledge that it is being called with sequential values of num
. That prevents a key optimisation that's possible if we inline the function. Then we're able to maintain variables to record the remainders without needing the %
operator.
Modified code
Incorporating my suggestions, and some improvements from other answers:
#!/usr/bin/python3
import sys
def fizzbuzz(max_num, fizz, buzz):
'''
A generator that transforms the range [1..max_num], returning each
number converted to string, unless it's an exact multiple of
'fizz' (when it returns the string "Fizz") or of 'buzz' (returns
"Buzz") - or for multiples of both divisors, the combined string "FizzBuzz".
>>> list(fizzbuzz(6, 2, 3))
['1', 'Fizz', 'Buzz', 'Fizz', '5', 'FizzBuzz']
>>> list(fizzbuzz(6, 2, 4))
['1', 'Fizz', '3', 'FizzBuzz', '5', 'Fizz']
'''
# accumulators tracking num % fizz and num % buzz
fizz_acc = 0
buzz_acc = 0
for num in range(1, max_num + 1):
fizz_acc += 1
buzz_acc += 1
result = ""
if fizz_acc == fizz:
fizz_acc = 0
result += "Fizz"
if buzz_acc == buzz:
buzz_acc = 0
result += "Buzz"
yield result if result else str(num)
def arg_or_input(argno, prompt):
'''
Read a value from the 'argno'th command-line argument,
or if there are fewer, use 'prompt' to input interactively.
'''
try:
return sys.argv[argno]
except IndexError:
return input(prompt)
if __name__ == '__main__':
import doctest
if doctest.testmod()[0]:
sys.exit(1)
try:
fizz = int(arg_or_input(1, "Enter the number to Fizz: "))
buzz = int(arg_or_input(2, "Enter the number to Buzz: "))
max_num = int(arg_or_input(3, "Enter the maximum number: "))
except ValueError:
print("Inputs must be integers!", file=sys.stderr)
sys.exit(1)
if fizz <= 0 or buzz <= 0 or max_num <= 0:
print("All inputs must be positive!", file=sys.stderr)
sys.exit(1)
for s in fizzbuzz(max_num, fizz, buzz):
print(s)
getString(4, 2, 4) == "Fizz"
when it should actually equal"FizzBuzz"
. This bug occurs whenfizz
andbuzz
are not coprime. \$\endgroup\$pylint
's standards. Possiblypep8
's too. \$\endgroup\$if num % fizz == 0 and num % buzz == 0:
\$\endgroup\$