У дроби не может быть нулевого знаменателя. Можно также договориться о едином представлении: знаменатель положительный, дробь сокращена, а ноль записывается как 0/1.
from math import gcd
def normalize(n, d):
if d == 0:
raise ValueError("zero denominator")
if d < 0:
n, d = -n, -d
g = gcd(n, d)
return n // g, d // g
print(normalize(2, -4)) # (-1, 2)
Для целых n и d функция возвращает согласованное представление. Но если дробь хранится в объекте, его методы тоже должны сохранять эти правила. Проверка только в конструкторе не поможет после изменения состояния.
Материал об инвариантах показывает, как формулировать такие условия. Для проверки внешнего ввода используйте явные исключения:
assert может быть отключён запуском Python с -O.
