Shay
Shay

Reputation: 1499

Duck typing with otherwise useless statements (remotely Pythonic)?

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

Answers (1)

blhsing
blhsing

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

Related Questions