Продолжим разработку класса Fraction
, который реализует предлагаемые дроби.
Предусмотрите возможность задать отрицательные числитель и/или знаменатель. А также перепишите методы __str__
и __repr__
таким образом, чтобы информация об объекте согласовывалась с инициализацией строкой.
Далее реализуйте оператор математического отрицания — унарный минус.
Примечание
Будем считать, что пользователь знает о запрете деления на ноль.
Все числа в данной задаче будут положительными.
Все поля и методы, не требуемые в задаче, следует инкапсулировать (называть с использованием ведущих символов нижнего подчёркивания).
Ваше решение должно содержать только классы и функции.
В решении не должно быть вызовов инициализации требуемых классов.
Пример
Ввод
a = Fraction(1, 3)
b = Fraction(-2, -6)
c = Fraction(-3, 9)
d = Fraction(4, -12)
print(a, b, c, d)
print(*map(repr, (a, b, c, d)))
Вывод
1/3 1/3 -1/3 -1/3
Fraction('1/3') Fraction('1/3') Fraction('-1/3') Fraction('-1/3')
Ввод
a = Fraction('-1/2')
b = -a
print(a, b, a is b)
b.numerator(-b.numerator())
a.denominator(-3)
print(a, b)
print(a.numerator(), a.denominator())
print(b.numerator(), b.denominator())
Вывод
-1/2 1/2 False
1/3 -1/2
1 3
1 2
Решение
Мы должны слегка модифицировать методы __str__ и __repr__. Кроме того, нам предлагают предусмотреть возможность задания отрицательных числителя и/или знаменателя. И тут мы попадаюм в ловушку произвола Яндекса при постановке задачи. Дело в том, что несмотря на то, что -(1/2), (-1)/2 и 1/(-2) по сути одна и та же дробь. Но в стандартном Fraction, отрицательнй знак принадлежит числителю, а знаменатель всегда положительный. В моей молодости в школе нас тоже призывали минус всегда переносить в числитель – это сильно упрощает все дальнейшие вычисления.
Из этого нюанса вытекает главная сложность этого задания. Когда учащиеся пытаются реализовать метод, изменяющий числитель, они получают ошибку, потому что Яндекс считает, что числитель дроби -1/2 равен единице. Хотя для большинства из нас и для стандартного модуля Fractions он будет равен -1. Соответственно, при замене числителя в отрицательной дроби важно не забыть учесть ее бывший знак, как того хочет Яндекс.
Я советую оставить знак в числителе, так как это достаточно сильно облегчит вам дальнейшие вычисления.
У меня для примирения сложившейся практики и требований Яндекса введено две модификации – добавлен метод __sign, который возвращает текущий знак дроби, проверяя числитель и внесено изменение в метод numerator для того, чтобы он всегда возвращал только положительное число. Напоминаю, что в стандартном модуле этот метод вернет отрицательное число, в случае если дробь отрицательная.
Посмотреть код
Решение
class Fraction():
def __init__(self, *args) -> None:
if isinstance(args[0], str):
self.__num, self.__den = [int(c) for c in args[0].split('/')]
else:
self.__num = args[0]
self.__den = args[1]
self.__reduction()
def __sign(self):
return -1 if self.__num < 0 else 1
def __neg__(self) -> 'Fraction':
return Fraction(-self.__num, self.__den)
def __gcd(self, a, b) -> int:
while b:
a, b = b, a % b
return abs(a)
def __reduction(self) -> 'Fraction':
__gcd = self.__gcd(self.__num, self.__den)
self.__num //= __gcd
self.__den //= __gcd
if self.__den < 0:
self.__num = -self.__num
self.__den = abs(self.__den)
return self
def __str__(self) -> str:
return f'{self.__num}/{self.__den}'
def __repr__(self) -> str:
return f"Fraction('{self.__num}/{self.__den}')"
def numerator(self, *args) -> int:
if len(args):
self.__num = args[0] * self.__sign()
self.__reduction()
return abs(self.__num)
def denominator(self, *args) -> int:
if len(args):
self.__den = args[0]
self.__reduction()
return abs(self.__den)
в чём то же должен быть смысл не принимать числитель дроби -1/2 равен единице
находить решение из не стандартных ситуаций
Добрый день, у Вас метод __neg__ возвращает значения с отрицательным числителем, при этом по коду он не нужен и нигде не вызывается
попробуйте без него сделать
на мой взгляд, без него не очень сработает и второй тест, потому что там есть
Да, я проверил, без него не работает, однако я не понимаю, почему он работает, если этот метод не вызывается
этот метод вызывается самим пайтоном, когда он встречает в коде операцию унарного минуса.
А, я понял. Спасибо большое!
Уважаемый Сергей, здравствуйте,
После длительного перерыва продолжил решать, подглядываю в Ваши ответы.
Объясните (или подскажите, где посмотреть), что это и зачем?
def __init__(self, *args) -> None: – такое (-> None), почему-то Вы пишите всегда
def __str__(self) -> str: -внесли в Дроби V0.1, у Яндекса и в других решениях нет-> str
def __gcd(self, a, b) -> int: – в первой задачке про Дроби -> int не было
def __reduction(self) -> ‘Fraction’: – аналогичный вопрос.
Где-то (возможно, у Вас видел про -> None), не могу найти теперь.
Посмотрел в ресурсе, где раньше (пока Ваш не нашел) подглядывал ответы – https://github.com/Pavellver/Yandex_handbook_answers/. Там таких конструкций нет.
Что это за конструкции, зачем они нужны, почему Вы их где-то применяете, а где-то – нет?
Спасибо.
Добрый день!
Все довольно просто. Эти инструкции нужны для специальных программ, которые делают язык python более строгим с точки зрения типов даных.
Несмотря на то, что программировать я учился очень давно и за свою жизнь использовал с десяток языков, плотно изучением python я занялся только пару лет назад, хоть до этого и довелось написать пару небольших, но крайне полезных программ на нем.
В процессе изучения языка я наткнулся на специальный класс программ – они называются линтеры (программы которые проверяют не только синтаксис, но и показывают возможные проблемы из-за несоответствия типов данных и много чего еще) эти записи появились именно в рамках изучения этих программ.
Они частично есть там, где я переписывал код для публикации на ресурсе (это называется рефакторинг). Но использую я их далеко не всегда и не везде. В основном, так оформляют код на серьезных проектах.
так например в описании функций тоже можно использовать более строгую типизацию:
def __gcd(self, a: int, b: int) -> int:
тогда если вы попробуете написать вызвов этого метода с использованием float, то получите предупреждение он линтера, что это неправильный тип данных.
Подробнее об линтерах можно почитать по этой ссылке на английском языке, а о типизации тут на русском.
Сергей,
Спасибо за разъяснения.
Сергей, здравствуйте.
Скажите, пожалуйста, почему класс указан как Fraction()?
В смысле, не class Fraction, а class Fraction()?
И в прошлой задаче было так… Что означают скобки?
Добрый день!
Просто привычка. С точки зрения задачи это равнозначные записи, никакого глубокого смысла не закладывал.
Они пригодятся, если вдруг мы объявляем класс, который наследует от другого класса. У меня в задаче про прямоугольники дальше будет пример с наследованием.
А, да-да, если вдруг будет наследование. Заранее подготовились, так сказать. На всякий случай. Возьму “на вооружение”.
Спасибо большое за как всегда подробное и очень доходчивое объяснение!