Reputation: 1499
The below will raise a TypeError for floats.
from math import factorial
def divide_factorials(a, b):
return factorial(a) / factorial(b)
>>> divide_factorials(6.1, 5)
*** ValueError: factorial() only accepts integral values
That's perfect until I want to incorporate an optimization.
def divide_factorials(a, b):
if a == b:
return 1
return factorial(a) / factorial(b)
Now I've lost my TypeError from floats where stop == start.
>>> divide_factorials(3.3, 3.3)
1
I could get my TypeError back with isinstance, but that requires my knowing exactly what will and will not work in factorial (or any other function I might be calling). It's tempting to do something like
def divide_factorials(a, b):
# duck type first
factorial((a + b) % 1)
if a == b:
return 1
return factorial(a) / factorial(b)
This seems more robust, but less obvious. For (I'm sure) good reasons, I haven't seen this pattern before. What's the best reason not to do it?
I could dress it up with assert or try/except, but
assert factorial((a + b) % 1) is not None
# or
try:
factorial((a + b) % 1)
except TypeError:
raise TypeError
seem, if anything, a bit MORE cryptic.
Upvotes: 2
Views: 66
Reputation: 106523
It's indeed quite cryptic to call a function that is meant to perform calculations for the sole purpose of making a type validation. It makes the intention of the code unclear, and therefore makes the code un-Pythonic.
I would simply repeat the same explicit type validation if it's important. Moreover, the right way to optimize factorial division is to not call a factorial function, but to iterate from the divisor plus one to the dividend and aggregate the product instead:
def divide_factorials(a, b):
if not all(isinstance(i, int) for i in (a, b)):
raise TypeError('divide_factorials() only accepts integral values')
product = 1
for i in range(b + 1, a + 1):
product *= i
return product
Upvotes: 1