Могу добавить, что при реализации __add__ лучше обращаться к своему классу через self.__class__, а не напрямую через название Clock. Потому что названия классов имеют свойство меняться, и ничего внутри класса ломаться при этом не должно. class Clock(): def __init__(self, seconds) -> None: self.__seconds = seconds def __add__(self, other): return self.__class__(self.__seconds + other) a = Clock(100) b = a + 100 print(b.__dict__)
@@minecraftlp6306 В твоем варианте меняется сам объект. Тогда в примере выше (b = a + 100) получится, что в результате такого "сложения" изменится сам объект "a" и еще оба идентификатора (и "a" и "b") будут ссылаться на один и тот же объект
Думаю, так будет уже лучше, я вынес в отдельную функцию основу кода и в методах уже вызывал ее и писал знаки def met(self, other): if not isinstance(other, (int, Clock)): raise ArithmeticError("Допускаешь ошибку") sc = other if isinstance(other, Clock): sc = other.seconds return sc def __sub__(self, other): sc = self.met(other) return Clock(self.seconds - sc) def __mul__(self, other): sc = self.met(other) return Clock(self.seconds * sc) def __floordiv__(self, other): sc = self.met(other) return Clock(self.seconds // sc)
Я единственный кто пытается решить задачу с помощью вспомогательного метода класса ? На сколько я понял и ознакомился с темой Дескрипторы созданы не для этих целей , но их кончено же можно и так прописать . На самом то деле они для больше подходят для управления доступом к атрибутам, а не для выполнения арифметических операций. Вот мое решение , сразу скажу не без посторонней помощи , я не гейний ))) сам голову долго ломал как лучше class Clock: __DAY = 86400 # Количество секунд в сутках def __init__(self, seconds: int): if not isinstance(seconds, int): raise TypeError('секунды должны быть целым числом') self.seconds = seconds % self.__DAY def get_time(self): s = self.seconds % 60 m = (self.seconds // 60) % 60 h = (self.seconds // 3600) % 24 return f'{self.__convert(h)}:{self.__convert(m)}:{self.__convert(s)}' @staticmethod def __convert(x): return str(x).rjust(2, '0') def _operate_time(self, other, operator): if not isinstance(other, int): raise TypeError('Операция допустима только с целыми числами') new_seconds = operator(self.seconds, other) % self.__DAY return Clock(new_seconds) def __add__(self, other): return self._operate_time(other, lambda x, y: x + y) def __sub__(self, other): return self._operate_time(other, lambda x, y: x - y) def __mul__(self, other): return self._operate_time(other, lambda x, y: x * y) def __truediv__(self, other): if other == 0: raise ZeroDivisionError("Нельзя делить на ноль") return self._operate_time(other, lambda x, y: int(x / y)) def __floordiv__(self, other): if other == 0: raise ZeroDivisionError("Нельзя делить на ноль") return self._operate_time(other, lambda x, y: x // y) def __mod__(self, other): if other == 0: raise ZeroDivisionError("Нельзя брать остаток от деления на ноль") return self._operate_time(other, lambda x, y: x % y) def __radd__(self, other): return self.__add__(other) def __rsub__(self, other): if not isinstance(other, int): raise TypeError('Можно отнимать только от целого числа') return Clock((other - self.seconds) % self.__DAY) def __rmul__(self, other): return self.__mul__(other)
При нахождении переменной h в функции get_time нет необходимости искать остаток от деления на 24. Ведь ты сам в инициализаторе сделал так, чтобы секунды не превышали число 86400. А исходя из этого, мы имеем то, что количество секунд в переводе на часы никогда не превысят отметки "24 часа".
Для класса Point из предыдущих уроков: class Points: # создание класса def __init__(self, x,y,z): # инициализация self.coords = [x,y,z] def __str__(self): # применяется, если объект класса делают строкой return (f'Координаты точки: '+ f'X = {self.coords[0]} '+ f'Y = {self.coords[1]} '+ f'Z = {self.coords[2]}')
def _operate(first,other,action): if isinstance(other, (int, float)): # проверяет тип второго аргумента return Points((eval(f'{first.coords[0]} {action} {other}')), (eval(f'{first.coords[1]} {action} {other}')), (eval(f'{first.coords[2]} {action} {other}'))) elif type(other) == Points: # позволит прибавлять к объекту другой объект класса return Points((eval(f'{first.coords[0]} {action} {other.coords[0]}')), (eval(f'{first.coords[1]} {action} {other.coords[1]}')), (eval(f'{first.coords[2]} {action} {other.coords[2]}'))) else: raise TypeError('второй аргумент должен быть числом или объектом класса') def __add__(self, other): # вызывается, если к объекту класса применить сложение return self._operate(other, '+') def __sub__(self, other): # вызывается, если к объекту класса применить вычитание return self._operate(other, '-') def __mul__(self, other): # вызывается, если к объекту класса применить умножение return self._operate(other, '*') def __truediv__(self, other): # вызывается, если к объекту класса применить деление return self._operate(other, '/') point1 = Points(16,42,12) point2 = Points(1,1,1) point3 = Points(3,3,3) point4 = point1 * point3 - point2 print(point4) ----------------вывод---------------- Координаты точки: X = 47 Y = 125 Z = 35
🤣все с точностью донаоборот....не прописывать каждую функцию и декорировать ее,а создать дескриптор который будет описывать всю шоблу этих функций)))))....просто поэкспериментируй с конструкцией __get__(): def inner(other): return ..... return inner
А не проще вместо __get_formatted сделать так: def get_time(self): """Получить текущее время.""" s = self.seconds % 60 m = self.seconds % 3600 // 60 h = self.seconds // 3600 return f'{h:02d}:{m:02d}:{s:02d}' И "h" зачем "% 24", когда это уже в __init__ сделали?
Вопрос: что будет если сложить два класса Clock(c1+c2) что вызовется(оба метода определены): с1.__add__(c2) или c2.__rand__(c1). В вашем примере разницы нет, но на практике это могут быть 2 разных класса и от порядка зависит то, какой класс возвращать.
Вроде бы принято при неподходящем типе операндов в арифметических методах не вызывать исключение, а возвращать константу NotImplemented. Это позволяет попытаться обратиться к соответствующему методу второго операнда прежде чем выкидывать ошибку.
Да, и я попровал в методе __add__ не создавать новый обьект класса, а изменять поле self.seconds и возвращать ссылку на текущий обьект класса. множественное сложение, и все другие виды сложений так же прекрасно работают. И программа соответственно должна работать быстрее, получается одни плюсы
@@СарматПересветов вы в add делаете так: return self.seconds + other тогда да, в самом объекте ничего меняться не будет. Это мой ляп. Но объект все же нужен (в общем случае), т.к. хорошо, что здесь числа складываются, а если бы мы комплексные числа складывали? Тогда без объекта не работало бы.
Вроде неплохо получилось def universal_operation(self, other, operation: str, for_myself: bool): if not isinstance(other, (int, Clock)): raise ArithmeticError(f'unsupported operand type(s) for +: {type(self)} and {type(other)}') sc = other if isinstance(other, Clock): sc = other.seconds
Насчёт домашки, можно либо сделать класс метод который считывает other и его уже превращает в цифарки и в ариф. методах делать self.seconds + func(other), либо сделать декоратор, и обернуть ариф. методы Собственно вот мой второй путь from functools import wraps class Clock: __DAY = 86400 def __init__(self, seconds: int): if not isinstance(seconds, int): raise TypeError("На*** иди") self.seconds = seconds % self.__DAY def get_time(self): s = self.seconds % 60 m = self.seconds // 60 % 60 h = self.seconds // 3600 return f"{h:02}:{m:02}:{s:02}" def __calculate(func): @wraps(func) def wrapper(self, other): if not isinstance(other, (int, Clock)): raise TypeError("Одумайся грешник") if isinstance(other, Clock): other = other.seconds return Clock(func(self.seconds, other)) return wrapper
@__calculate def __add__(self, other): return self + other def __radd__(self, other): return self + other @__calculate def __sub__(self, other): return self - other @__calculate def __mul__(self, other): return self * other @__calculate def __floordiv__(self, other): return self // other
Вопрос, почему в методе __add__ мы в место того что бы создавать новый обьект класса Clock не можем просто увеличить поле seconds на передаваемое в параметрах число? ведь в таком случае программа будет работать гораздо быстрее, т.к. на создание нового обьекта класса уходит гораздо больше (по компьютерным меркам) времени?
Насколько я понимаю, именно потому, что мы не всегда хотим при сложении менять исходный экземпляр класса. Например: c2 = c1 + 100 тут нам не нужно, чтобы менялся c1.seconds. А нужно чтобы функция возвращала новый экземпляр и присваивала его переменной c2
А зачем нужна __get_formated() функция? Проще же так: return f'{h:02}:{m:02}:{s:02}'. Ладно, буду считать, что это тренировка использования декоратора @classmethod
Очень советую избегать этих магических методов (именно этих 4-х). Лучше сделайте отдельные обычные методы add(), sub() или назовите какими-нибудь increase(), decrease(). И в методах просто соответственно увеличивайте или уменьшайте свойство seconds. Если же вы все-таки решили добавить логику сложения объектов с этими маг-методами, то обязательно протестируйте результат на всех возможных вариантах: объект справа от плюса, слева от плюса, складываются два объекта и результат присваивается третьему. И обязательно каждый раз проверяйте все свойства (в примере из видео это одно свойство seconds) всех объектов. А вот если вы создадите дочерний класс от этого, гемор вам обеспечен 😁
поправьте меня, если я не до конца правильно понимаю. Мне кажется что 5:08 минуте метод __get_formatted правильнее сделать статическим методом а не методом класса потому что он используется в методе экземпляра класса (в объекте). Понятно что будет работать и так и так, но все же.... ?
Ебать, хорош, мужик. Также подумал. Хорошо что не один такой. Вот так прописал: @staticmethod def ver_sec(x): if not isinstance(x, int): raise TypeError('need int')
Кажется, что было бы правильнее в методе __add__ поступить также как и с __iadd__. Так, в методе __add__ нам не придется создавать новый экземпляр (мы можем просто увеличить атрибут seconds, а затем вернуть self). Или мне не правильно кажется?) def __add__(self, other): if not isinstance(other, (int, Clock)): raise ArithmeticError("Adding number must be int") sc = other if isinstance(other, Clock): sc = other.seconds self.seconds += sc return self # return Clock(self.seconds + sc)
А Вы прогоните свой вариант на объектах класса например p1 = Class(100), p2 = Class(200) и p3 = p1 + p2, а потом выведите что находится в p1, p2, p3 и поймете почему именно у __iadd__ другая реализация
Можно тупой вопрос)) Вы говорили, что лучше использовать ссылку на класс, когда его где-то используешь в коде, чтобы не положить программу при замене названия. Можно ли тут использовать cls вместо Clock ?
Почему вначале при сложении мы создаём новый Экземпляр Класса, а потом (+=) мы просто меняем значение Атрибута Экземпляра Класса. Почему в первом случае мы не можем поменять? Зачем нам создавать новый Экземпляр Класса.
А зачем часы ещё делить по остатку на 24, если в принципе их не может быть больше 24, благодаря делению секунд на __DAY, или я чего-то не понимаю? И почему мы используем метод класса вместо статик метода, если мы не работаем с атрибутами класса?
А можно подсказку по том, как без дублирования кода засунуть все арифметические операции в класс, ведь по сути код для маг метода __add__ отличается от __sub__, __mul__, __truediv__ только названием метода и конкретной операцией. Через каррирование загнав все маг методы в словарь с соответствующим оператором, или видео урок про дескрипторы как раз здесь и можно применить, или я вовсе перемудрил и решение куда проще? Проверки на int, Clock тоже можно было засунуть в отдельный @classmetod для соблюдения "dry" это бы упростило читабельность и громоздкость кода? Заранее спасибо..
class Desc: def __init__(self): self.opdct={'add':'+','sub':'-','mul':'*','truediv':'/'} def __set_name__(self,owner,name): name=name.strip('_') self.prefix=name[0] if name.startswith(('i','r')) else None self.name=name[1:] if self.prefix else name def __get__(self,obj,owner): def wrapp(other): if type(other) in (int,float): n1,n2=obj.num,other else: n1,n2=obj.num,other.num if not self.prefix: return owner(self.op(self.opdct[self.name],n1,n2)) if self.prefix=='r': return owner(self.op(self.opdct[self.name],n2,n1)) if self.prefix=='i': setattr(obj,'num',self.op(self.opdct[self.name],n1,n2)) return obj return wrapp
@staticmethod def op(opsign,n1,n2): return eval(f'{n1}{opsign}{n2}') class A: __add__=Desc() __sub__=Desc() __mul__=Desc() __truediv__=Desc() __radd__=Desc() __rsub__=Desc() __rmul__=Desc() __rtruediv__=Desc() __iadd__=Desc() __isub__=Desc() __imul__=Desc() __itruediv__=Desc() def __init__(self,num): но это все самая первая проба....развитие и улучшение в моих решениях на степике суть метода использование non-data дескрипторов и замыканий в _get_ типовая конструкция такая def __get__(self,object,owner): def inner(other): return ... return inner а для работы с методами такая еще конструкция return owner(getattr(object.param,self.name)(other.param))-если нам например нужно получить результат арифметической операции над объектом как новый объект с параметром -результатом операции над 2 объектами КОРОЧЕ ПАСАНЫ ,,,,Я ЭТИ ВАШИ МАГМЕТОДЫ НА ДЕСКРИПТОРЕ ВЕРТЕЛ КАК ХОТЕЛ))))))
Сергей, а почему при использовании .rjust(2, 0) вы говорите, что нолик добавляется справа? Ведь он добавляется слева от цифр. Можно конечно предположить, что цифра смотрит на нас "лицом" и то, что мы воспринимаем как слева от неё, это на самом деле справа, но в таком случае при объяснении __add__(self, other) вы объясняете, что other это то, что стоит права от c1
@@selfedu_rus но ведь и метод почему-то начинается на r, а тот, что добавляет символы справа на l, какая-то путаница. А ещё подскажите, планируете ли вы делать на stepik курсы по джанго, базам данных и всему тому, что необходимо для бэкенда? Ролики на эти темы у вас на канале есть, но без практических задач сухая теория совсем не усваивается
@@selfedu_rus надеюсь, что решение будет найдено. Низкий вам поклон и благодарность за то, что вы для всех нас делаете, учиться у вас - одно удовольствие!
После переопределения метода __iadd__ решил убедиться, что новый экземпляр с1 не создается. Для этого выполнил печать следующим образом: c1 = Clock(1000) print("c1 =", c1, "=", c1.get_time()) c1 += 100 print("c1 =", c1, "=", c1.get_time()) в результате получил: c1 = = 00:16:40 __iadd__ c1 = = 00:20:00 т.е., адрес изменился, значит, новый экземпляр таки был создан?
Комментарий неправильный, я ошибся при копировании реализации метода. после исправления ошибки получил: c1 = = 00:21:40 __iadd__ c1 = = 00:23:20 Новый экземпляр не создается
Добрый день. Сориентируйте, пожалуйста, почему мы присваиваем функции get_formatted уровень класса @classmethod? В нем же не используются атрибуты класса, тот же __DAY. Заранее благодарна!
Благодарю за столь оперативный ответ! У меня появился ещё вопрос, если позволите. В коде мы в явном виде прописываем класс Clock, когда нам надо создавать экземпляры объектов для сложения в ф-ии __add__ и когда делаем проверку на принадлежность other к классу. Правильно ли я понимаю, что в данном случае мы никоим образом не можем уйти от прямого обращения к классу, заменив на cls или self, как делали в других уроках? Ведь если наименование класса Clock изменится, то потребуется менять наименование во всех местах, где было прямое обращение. Надеюсь, мой вопрос понятен :)
У меня освободилось кучу времени, и прохожу курс в степик. (Но я очень далеко... На 3.7... ((() Этот курс будет там еще долго? Уважаю ваш труд. Вы гений.
почему на 23 строчке нельзя было написать if isinstance(other, Clock): other = other.seconds return Clock(self.seconds + other) без введения доп переменной
Не понятно, если эти методы из коробки идут, то зачем в них еще столько кода писать, почему они сразу в две или одну строчку не идут? или я не про то? )
не про то, можно их вообще не переопределять, если не нужно, в противном случае прописываем свой алгоритм, а сколько строчек кода - это зависит от задачи
Не понятно как происходит вызов метода radd. Если я выполняю с1 + 100, происходит вызов с1.__add__(100). Вызывая 100 + с1, я ожидаю вызов 100.__add__(c1), т. Е метод класса int. Как интерпретатор понимает, что нужно вызвать именно radd нашего класса?
попытался превратить магию методов в вуду дескрипторов и замыканий class Desc: def __init__(self): self.opdct={'add':'+','sub':'-','mul':'*','truediv':'/'} def __set_name__(self,owner,name): name=name.strip('_') self.prefix=name[0] if name.startswith(('i','r')) else None self.name=name[1:] if self.prefix else name def __get__(self,obj,owner): def wrapp(other): if type(other) in (int,float): n1,n2=obj.num,other else: n1,n2=obj.num,other.num if not self.prefix: return owner(self.op(self.opdct[self.name],n1,n2)) if self.prefix=='r': return owner(self.op(self.opdct[self.name],n2,n1)) if self.prefix=='i': setattr(obj,'num',self.op(self.opdct[self.name],n1,n2)) return obj return wrapp
@staticmethod def op(opsign,n1,n2): return eval(f'{n1}{opsign}{n2}') class A: __add__=Desc() __sub__=Desc() __mul__=Desc() __truediv__=Desc() __radd__=Desc() __rsub__=Desc() __rmul__=Desc() __rtruediv__=Desc() __iadd__=Desc() __isub__=Desc() __imul__=Desc() __itruediv__=Desc() def __init__(self,num): но это все самая первая проба....развитие и улучшение в моих решениях на степике суть метода использование non-data дескрипторов и замыканий в __get__ типовая конструкция такая def __get__(self,object,owner): def inner(other): return ... return inner а для работы с методами такая еще конструкция return owner(getattr(object.param,self.name)(other.param))-если нам например нужно получить результат арифметической операции над объектом как новый объект с параметром -результатом операции над 2 объектами КОРОЧЕ ПАСАНЫ ,,,,Я ЭТИ ВАШИ МАГМЕТОДЫ НА ДЕСКРИПТОРЕ ВЕРТЕЛ КАК ХОТЕЛ))))))
а зачем мы в качестве возвращаемого значения метода __add__ устанавливаем НОВЫЙ объект класса Clock с увеличенным количеством секунд: return Clock(self.seconds + other) вместо того, чтобы просто увеличить значение для текущего объекта: return self.seconds + other ?
или это просто демонстрация того, как можно манипулировать данными, а нам на практике уже нужно будет действовать на своё усмотрение в зависимости от ситуации?
В данной реализации вернется количество секунд, а не экземпляр класса Clock. Если требуется изменить атрибут конкретного класса и вернуть экземпляр этого же класса (без создания нового), предлагаю примерно такой вариант: def __add__(self, other): if not isinstance(other, (int, Clock)): raise ArithmeticError("Атрибут должен быть целым числом (int) и объектом класса Clock") if isinstance(other, int): self._seconds += other if isinstance(other, Clock): self._seconds += other._seconds return self
Меня месяц тревожит вопрос, проиллюстрирую на примере из видео: c1 = Clock(1000) c1 = c1 + 100 Как сделать, чтобы работало и c1 = 100 + c1 ? Заранее спасибо)
@@selfedu_rus вряд-ли увижу ответ, но попытаюсь) В __radd__ мы получаем self и изменяя экземпляр через него, по сути методом и запишем значение, или я не прав? self ведь получается берет как раз что нам и нужно...
Как понять незначищий ноль справа , а потом говорите если 1 2 или то будет 0 1 0 2 и тд., где логика? Там помойму число больше нуля вправа едет а нули влево
Материал и подача хорошие, но английский плох. Рекомендую подучить произношение слов, которые используются в программировании. Неправильное произношение вызывает недоумение у незнающих людей. Ещё было бы полезно объяснять полное название методов, например mul - multiply(умножать). В данном видео метод __add__ читается как "эд" (добавить)
опять бессмысленное усложнение объяснения на ровном месте, с этими секундами, сначала надо понять суть на примитиве, потом рассчитывать космический корабль, часть ресурсов мозга уходит что бы вникнуть ещё и в пример ...
Могу добавить, что при реализации __add__ лучше обращаться к своему классу через self.__class__, а не напрямую через название Clock. Потому что названия классов имеют свойство меняться, и ничего внутри класса ломаться при этом не должно.
class Clock():
def __init__(self, seconds) -> None:
self.__seconds = seconds
def __add__(self, other):
return self.__class__(self.__seconds + other)
a = Clock(100)
b = a + 100
print(b.__dict__)
я бы сделал удобочитаемей:
def __add__(self, sec):
self.__seconds += sec
return self
@@minecraftlp6306 В твоем варианте меняется сам объект. Тогда в примере выше (b = a + 100) получится, что в результате такого "сложения" изменится сам объект "a" и еще оба идентификатора (и "a" и "b") будут ссылаться на один и тот же объект
Просто кладезь знаний 👍
Следующая ступенька, после ООП, паттерны 🙏
Идеальный урок.. Моё искреннее почтение Вашему труду... Передача Ваших знаний в массы бесценно...
Думаю, так будет уже лучше, я вынес в отдельную функцию основу кода и в методах уже вызывал ее и писал знаки
def met(self, other):
if not isinstance(other, (int, Clock)):
raise ArithmeticError("Допускаешь ошибку")
sc = other
if isinstance(other, Clock):
sc = other.seconds
return sc
def __sub__(self, other):
sc = self.met(other)
return Clock(self.seconds - sc)
def __mul__(self, other):
sc = self.met(other)
return Clock(self.seconds * sc)
def __floordiv__(self, other):
sc = self.met(other)
return Clock(self.seconds // sc)
Очень хорошо объяснили, а то я не мог понять как это работает, после вашего объяснения все стало на свои места, спасибо за ваш труд
Я единственный кто пытается решить задачу с помощью вспомогательного метода класса ? На сколько я понял и ознакомился с темой Дескрипторы созданы не для этих целей , но их кончено же можно и так прописать . На самом то деле они для больше подходят для управления доступом к атрибутам, а не для выполнения арифметических операций. Вот мое решение , сразу скажу не без посторонней помощи , я не гейний ))) сам голову долго ломал как лучше
class Clock:
__DAY = 86400 # Количество секунд в сутках
def __init__(self, seconds: int):
if not isinstance(seconds, int):
raise TypeError('секунды должны быть целым числом')
self.seconds = seconds % self.__DAY
def get_time(self):
s = self.seconds % 60
m = (self.seconds // 60) % 60
h = (self.seconds // 3600) % 24
return f'{self.__convert(h)}:{self.__convert(m)}:{self.__convert(s)}'
@staticmethod
def __convert(x):
return str(x).rjust(2, '0')
def _operate_time(self, other, operator):
if not isinstance(other, int):
raise TypeError('Операция допустима только с целыми числами')
new_seconds = operator(self.seconds, other) % self.__DAY
return Clock(new_seconds)
def __add__(self, other):
return self._operate_time(other, lambda x, y: x + y)
def __sub__(self, other):
return self._operate_time(other, lambda x, y: x - y)
def __mul__(self, other):
return self._operate_time(other, lambda x, y: x * y)
def __truediv__(self, other):
if other == 0:
raise ZeroDivisionError("Нельзя делить на ноль")
return self._operate_time(other, lambda x, y: int(x / y))
def __floordiv__(self, other):
if other == 0:
raise ZeroDivisionError("Нельзя делить на ноль")
return self._operate_time(other, lambda x, y: x // y)
def __mod__(self, other):
if other == 0:
raise ZeroDivisionError("Нельзя брать остаток от деления на ноль")
return self._operate_time(other, lambda x, y: x % y)
def __radd__(self, other):
return self.__add__(other)
def __rsub__(self, other):
if not isinstance(other, int):
raise TypeError('Можно отнимать только от целого числа')
return Clock((other - self.seconds) % self.__DAY)
def __rmul__(self, other):
return self.__mul__(other)
Вторая неделя изучения ооп!!!!1!1!?1!1?2++1(#-1)2
Если говорить короче,то иду хорошо, спасибо огромное автору за отличное объяснение материала❤
При нахождении переменной h в функции get_time нет необходимости искать остаток от деления на 24. Ведь ты сам в инициализаторе сделал так, чтобы секунды не превышали число 86400. А исходя из этого, мы имеем то, что количество секунд в переводе на часы никогда не превысят отметки "24 часа".
Привычка все проверять на всех уровнях.
Спасибо. Ничего себе, чего можно наворотить.
Спасибо. Как всегда все доходчиво и понятно!
Для класса Point из предыдущих уроков:
class Points: # создание класса
def __init__(self, x,y,z): # инициализация
self.coords = [x,y,z]
def __str__(self): # применяется, если объект класса делают строкой
return (f'Координаты точки:
'+
f'X = {self.coords[0]}
'+
f'Y = {self.coords[1]}
'+
f'Z = {self.coords[2]}')
def _operate(first,other,action):
if isinstance(other, (int, float)): # проверяет тип второго аргумента
return Points((eval(f'{first.coords[0]} {action} {other}')),
(eval(f'{first.coords[1]} {action} {other}')),
(eval(f'{first.coords[2]} {action} {other}')))
elif type(other) == Points: # позволит прибавлять к объекту другой объект класса
return Points((eval(f'{first.coords[0]} {action} {other.coords[0]}')),
(eval(f'{first.coords[1]} {action} {other.coords[1]}')),
(eval(f'{first.coords[2]} {action} {other.coords[2]}')))
else:
raise TypeError('второй аргумент должен быть числом или объектом класса')
def __add__(self, other): # вызывается, если к объекту класса применить сложение
return self._operate(other, '+')
def __sub__(self, other): # вызывается, если к объекту класса применить вычитание
return self._operate(other, '-')
def __mul__(self, other): # вызывается, если к объекту класса применить умножение
return self._operate(other, '*')
def __truediv__(self, other): # вызывается, если к объекту класса применить деление
return self._operate(other, '/')
point1 = Points(16,42,12)
point2 = Points(1,1,1)
point3 = Points(3,3,3)
point4 = point1 * point3 - point2
print(point4)
----------------вывод----------------
Координаты точки:
X = 47
Y = 125
Z = 35
Сергей, спасибо за уроки!!!
класс! Серёга лучший!
Снимаю шляпю и самый низкий поклон до колени !!!
Спасибо. Очень интересно.
Спасибо крутое ДЗ:
class Clock:
__DAY = 86400
def __init__(self,seconds:int):
if not isinstance(seconds,(int,float)):
raise TypeError("Must be int or flot")
self.seconds = seconds % self.__DAY
def get_time(self):
s = self.seconds % 60
m = (self.seconds // 60) % 60
h = (self.seconds // 3600) % 24
return f"{self.__get_formatted(h)}:{self.__get_formatted(m)}:{self.__get_formatted(s)}"
@classmethod
def __get_formatted(cls,x):
return str(x).rjust(2, "0")
def process(func):
def inner(self, other):
if not isinstance(other, (int, Clock)):
raise ArithmeticError(" Right operant must be int")
sc = other
if isinstance(other, Clock):
sc = other.seconds
return Clock(func(self.seconds, sc))
return inner
@process
def __add__(self, other):
return self + other
def __radd__(self, other):
return self + other
@process
def __sub__(self, other):
return self - other
@process
def __mul__(self, other):
return self * other
@process
def __truediv__(self, other):
return self / other
c1 = Clock(1000)
c2 = Clock(2000)
print(c1.get_time())
print(c2.get_time())
c3 = c1 + c2
print(c3.get_time())
c4 = c3 - c2
print(c4.get_time())
c5 = c2 / c1
print(c5.get_time())
🤣все с точностью донаоборот....не прописывать каждую функцию и декорировать ее,а создать дескриптор который будет описывать всю шоблу этих функций)))))....просто поэкспериментируй с конструкцией __get__():
def inner(other):
return .....
return inner
во, твой вариант мне прям понравился
Очень доступно, спасибо!
Перегрузки операторов в с++ и подобных языках аналогично работают
А не проще вместо __get_formatted сделать так:
def get_time(self):
"""Получить текущее время."""
s = self.seconds % 60
m = self.seconds % 3600 // 60
h = self.seconds // 3600
return f'{h:02d}:{m:02d}:{s:02d}'
И "h" зачем "% 24", когда это уже в __init__ сделали?
вроде и понял, но повторить или добавить свое точно не получится, тем кто понимает и применяет такое сразу, завидую)
спасибо за урок!
Вопрос: что будет если сложить два класса Clock(c1+c2) что вызовется(оба метода определены): с1.__add__(c2) или c2.__rand__(c1). В вашем примере разницы нет, но на практике это могут быть 2 разных класса и от порядка зависит то, какой класс возвращать.
Это гениально
Спасибо за уроки
Было бы элегантней написать sc = other.seconds if isinstance(other, Click) else other)
Вроде бы принято при неподходящем типе операндов в арифметических методах не вызывать исключение, а возвращать константу NotImplemented. Это позволяет попытаться обратиться к соответствующему методу второго операнда прежде чем выкидывать ошибку.
Ураа, домашка)))
спасибо!👏👍
Да, и я попровал в методе __add__ не создавать новый обьект класса, а изменять поле self.seconds и возвращать ссылку на текущий обьект класса. множественное сложение, и все другие виды сложений так же прекрасно работают. И программа соответственно должна работать быстрее, получается одни плюсы
так у вас и значения в объектах, участвующих в сложениях будут тогда меняться
@@selfedu_rus ну, вот сейчас посмотрел, не изменились значения в обьектах участвующих в сложении, все работает так как и должно
@@СарматПересветов вы в add делаете так:
return self.seconds + other
тогда да, в самом объекте ничего меняться не будет. Это мой ляп. Но объект все же нужен (в общем случае), т.к. хорошо, что здесь числа складываются, а если бы мы комплексные числа складывали? Тогда без объекта не работало бы.
@@selfedu_rus Понял, спасибо)
А зачем мы метод "get_formated" сделали методом класса? Статическим не правильнее?
Возможности f-строк в Python позволяют обойтись без метода __get_formatted() вот таким образом: {self.h:>02}
Вроде неплохо получилось
def universal_operation(self, other, operation: str, for_myself: bool):
if not isinstance(other, (int, Clock)):
raise ArithmeticError(f'unsupported operand type(s) for +: {type(self)} and {type(other)}')
sc = other
if isinstance(other, Clock):
sc = other.seconds
if not for_myself:
if operation == '+':
answer = Clock(self.seconds + sc)
elif operation == '-':
answer = Clock(self.seconds - sc)
return answer
else:
if operation == '+':
self.seconds = (self.seconds + sc) % self.__DAY
elif operation == '-':
self.seconds = (self.seconds - sc) % self.__DAY
return self
Насчёт домашки, можно либо сделать класс метод который считывает other и его уже превращает в цифарки и в ариф. методах делать self.seconds + func(other), либо сделать декоратор, и обернуть ариф. методы
Собственно вот мой второй путь
from functools import wraps
class Clock:
__DAY = 86400
def __init__(self, seconds: int):
if not isinstance(seconds, int):
raise TypeError("На*** иди")
self.seconds = seconds % self.__DAY
def get_time(self):
s = self.seconds % 60
m = self.seconds // 60 % 60
h = self.seconds // 3600
return f"{h:02}:{m:02}:{s:02}"
def __calculate(func):
@wraps(func)
def wrapper(self, other):
if not isinstance(other, (int, Clock)):
raise TypeError("Одумайся грешник")
if isinstance(other, Clock):
other = other.seconds
return Clock(func(self.seconds, other))
return wrapper
@__calculate
def __add__(self, other):
return self + other
def __radd__(self, other):
return self + other
@__calculate
def __sub__(self, other):
return self - other
@__calculate
def __mul__(self, other):
return self * other
@__calculate
def __floordiv__(self, other):
return self // other
Вопрос, почему в методе __add__ мы в место того что бы создавать новый обьект класса Clock не можем просто увеличить поле seconds на передаваемое в параметрах число? ведь в таком случае программа будет работать гораздо быстрее, т.к. на создание нового обьекта класса уходит гораздо больше (по компьютерным меркам) времени?
Насколько я понимаю, именно потому, что мы не всегда хотим при сложении менять исходный экземпляр класса. Например:
c2 = c1 + 100
тут нам не нужно, чтобы менялся c1.seconds. А нужно чтобы функция возвращала новый экземпляр и присваивала его переменной c2
@selfedu Серёга молодец конечно. Но ты не сказал зачем нужны эти методы на практике.
А зачем нужна __get_formated() функция? Проще же так: return f'{h:02}:{m:02}:{s:02}'. Ладно, буду считать, что это тренировка использования декоратора @classmethod
Очень советую избегать этих магических методов (именно этих 4-х). Лучше сделайте отдельные обычные методы add(), sub() или назовите какими-нибудь increase(), decrease(). И в методах просто соответственно увеличивайте или уменьшайте свойство seconds.
Если же вы все-таки решили добавить логику сложения объектов с этими маг-методами, то обязательно протестируйте результат на всех возможных вариантах: объект справа от плюса, слева от плюса, складываются два объекта и результат присваивается третьему. И обязательно каждый раз проверяйте все свойства (в примере из видео это одно свойство seconds) всех объектов.
А вот если вы создадите дочерний класс от этого, гемор вам обеспечен 😁
поправьте меня, если я не до конца правильно понимаю. Мне кажется что 5:08 минуте метод __get_formatted правильнее сделать статическим методом а не методом класса потому что он используется в методе экземпляра класса (в объекте). Понятно что будет работать и так и так, но все же.... ?
Ебать, хорош, мужик. Также подумал. Хорошо что не один такой. Вот так прописал:
@staticmethod
def ver_sec(x):
if not isinstance(x, int):
raise TypeError('need int')
Кажется, что было бы правильнее в методе __add__ поступить также как и с __iadd__. Так, в методе __add__ нам не придется создавать новый экземпляр (мы можем просто увеличить атрибут seconds, а затем вернуть self). Или мне не правильно кажется?)
def __add__(self, other):
if not isinstance(other, (int, Clock)):
raise ArithmeticError("Adding number must be int")
sc = other
if isinstance(other, Clock):
sc = other.seconds
self.seconds += sc
return self
# return Clock(self.seconds + sc)
А Вы прогоните свой вариант на объектах класса например p1 = Class(100), p2 = Class(200) и p3 = p1 + p2, а потом выведите что находится в p1, p2, p3 и поймете почему именно у __iadd__ другая реализация
Ех зная это можно было бы задание в 3.3 последние легче сделать)))
Можно тупой вопрос)) Вы говорили, что лучше использовать ссылку на класс, когда его где-то используешь в коде, чтобы не положить программу при замене названия.
Можно ли тут использовать cls вместо Clock ?
Вместо Clock используй self.__class__, перед и после class двойное нижнее подчеркивание
зачем было городить еще и __get_formatted() ? можно было все уместить в f-строку
Почему вначале при сложении мы создаём новый Экземпляр Класса, а потом (+=) мы просто меняем значение Атрибута Экземпляра Класса.
Почему в первом случае мы не можем поменять? Зачем нам создавать новый Экземпляр Класса.
Чтобы не было дублирования кода - оставшиеся операторы сделать через дескрипторы?
Если передать в seconds bool то TypeError не возникнет
А зачем часы ещё делить по остатку на 24, если в принципе их не может быть больше 24, благодаря делению секунд на __DAY, или я чего-то не понимаю? И почему мы используем метод класса вместо статик метода, если мы не работаем с атрибутами класса?
на 24 делю для надежности, чтобы точно не было больше (привычка везде делать "защиту от дурака"), а метод да, здесь вполне и статик подойдет.
А можно подсказку по том, как без дублирования кода засунуть все арифметические операции в класс, ведь по сути код для маг метода __add__ отличается от __sub__, __mul__, __truediv__ только названием метода и конкретной операцией. Через каррирование загнав все маг методы в словарь с соответствующим оператором, или видео урок про дескрипторы как раз здесь и можно применить, или я вовсе перемудрил и решение куда проще?
Проверки на int, Clock тоже можно было засунуть в отдельный @classmetod для соблюдения "dry" это бы упростило читабельность и громоздкость кода?
Заранее спасибо..
Я думаю словарь нужно составить 'операция':'знак операции' и использовать eval()
class Desc:
def __init__(self):
self.opdct={'add':'+','sub':'-','mul':'*','truediv':'/'}
def __set_name__(self,owner,name):
name=name.strip('_')
self.prefix=name[0] if name.startswith(('i','r')) else None
self.name=name[1:] if self.prefix else name
def __get__(self,obj,owner):
def wrapp(other):
if type(other) in (int,float):
n1,n2=obj.num,other
else:
n1,n2=obj.num,other.num
if not self.prefix:
return owner(self.op(self.opdct[self.name],n1,n2))
if self.prefix=='r':
return owner(self.op(self.opdct[self.name],n2,n1))
if self.prefix=='i':
setattr(obj,'num',self.op(self.opdct[self.name],n1,n2))
return obj
return wrapp
@staticmethod
def op(opsign,n1,n2):
return eval(f'{n1}{opsign}{n2}')
class A:
__add__=Desc()
__sub__=Desc()
__mul__=Desc()
__truediv__=Desc()
__radd__=Desc()
__rsub__=Desc()
__rmul__=Desc()
__rtruediv__=Desc()
__iadd__=Desc()
__isub__=Desc()
__imul__=Desc()
__itruediv__=Desc()
def __init__(self,num):
но это все самая первая проба....развитие и улучшение в моих решениях на степике
суть метода использование non-data дескрипторов и замыканий в _get_
типовая конструкция такая
def __get__(self,object,owner):
def inner(other):
return ...
return inner
а для работы с методами такая еще конструкция return owner(getattr(object.param,self.name)(other.param))-если нам например нужно получить результат арифметической операции над объектом как новый объект с параметром -результатом операции над 2 объектами
КОРОЧЕ ПАСАНЫ ,,,,Я ЭТИ ВАШИ МАГМЕТОДЫ НА ДЕСКРИПТОРЕ ВЕРТЕЛ КАК ХОТЕЛ))))))
Сергей, а почему при использовании .rjust(2, 0) вы говорите, что нолик добавляется справа? Ведь он добавляется слева от цифр.
Можно конечно предположить, что цифра смотрит на нас "лицом" и то, что мы воспринимаем как слева от неё, это на самом деле справа, но в таком случае при объяснении __add__(self, other) вы объясняете, что other это то, что стоит права от c1
да я просто оговорился! Конечно, слева! ))
@@selfedu_rus но ведь и метод почему-то начинается на r, а тот, что добавляет символы справа на l, какая-то путаница.
А ещё подскажите, планируете ли вы делать на stepik курсы по джанго, базам данных и всему тому, что необходимо для бэкенда? Ролики на эти темы у вас на канале есть, но без практических задач сухая теория совсем не усваивается
думал про Django, но на Stepik сложно сделать хороший курс по нему, т.к. это ж фреймворк. Это останавливает.
@@selfedu_rus надеюсь, что решение будет найдено.
Низкий вам поклон и благодарность за то, что вы для всех нас делаете, учиться у вас - одно удовольствие!
@@selfedu_rus Я думаю если найдете решение и сделайте курс платным будет пользоваться спросом так как нормального курса по джанго нет сейчас
👍
После переопределения метода __iadd__ решил убедиться, что новый экземпляр с1 не создается. Для этого выполнил печать следующим образом:
c1 = Clock(1000)
print("c1 =", c1, "=", c1.get_time())
c1 += 100
print("c1 =", c1, "=", c1.get_time())
в результате получил:
c1 = = 00:16:40
__iadd__
c1 = = 00:20:00
т.е., адрес изменился, значит, новый экземпляр таки был создан?
Комментарий неправильный, я ошибся при копировании реализации метода. после исправления ошибки получил:
c1 = = 00:21:40
__iadd__
c1 = = 00:23:20
Новый экземпляр не создается
Добрый день. Сориентируйте, пожалуйста, почему мы присваиваем функции get_formatted уровень класса @classmethod? В нем же не используются атрибуты класса, тот же __DAY. Заранее благодарна!
да, здесь вполне можно прописать статик, все верно!
Благодарю за столь оперативный ответ!
У меня появился ещё вопрос, если позволите. В коде мы в явном виде прописываем класс Clock, когда нам надо создавать экземпляры объектов для сложения в ф-ии __add__ и когда делаем проверку на принадлежность other к классу. Правильно ли я понимаю, что в данном случае мы никоим образом не можем уйти от прямого обращения к классу, заменив на cls или self, как делали в других уроках? Ведь если наименование класса Clock изменится, то потребуется менять наименование во всех местах, где было прямое обращение. Надеюсь, мой вопрос понятен :)
Нашла ответ на свой вопрос у Евгения ниже)
Ещё раз спасибо за Вашу подачу материала!
Урок #14 = Пройден
У меня освободилось кучу времени, и прохожу курс в степик. (Но я очень далеко... На 3.7... ((()
Этот курс будет там еще долго?
Уважаю ваш труд. Вы гений.
Спасибо! Да, я надеюсь, курс надолго )
@@selfedu_rus у вас есть курс на степике?
@@ИгорьХамула-х7я по ООП нет
@@ИгорьХамула-х7я есть))
В проверке isinstance значения bool проходят как int. Возможно корректнее через type?
да, если нужно четкое сравнение на типы, то конечно, type
почему на 23 строчке нельзя было написать
if isinstance(other, Clock):
other = other.seconds
return Clock(self.seconds + other)
без введения доп переменной
Дякую за відео, але не дуже зрозумів тут 12:02, що означає sc = other.seconds
Там при сложении операнд справа может быть как число, так и объект Clock. Отсюда и появляется проверка в add.
Не понятно, если эти методы из коробки идут, то зачем в них еще столько кода писать, почему они сразу в две или одну строчку не идут? или я не про то? )
не про то, можно их вообще не переопределять, если не нужно, в противном случае прописываем свой алгоритм, а сколько строчек кода - это зависит от задачи
Не понятно как происходит вызов метода radd. Если я выполняю с1 + 100, происходит вызов с1.__add__(100). Вызывая 100 + с1, я ожидаю вызов 100.__add__(c1), т. Е метод класса int. Как интерпретатор понимает, что нужно вызвать именно radd нашего класса?
здесь сам интерпретатор уже "соображает", что нужно вызвать именно radd, т.к. такой метод определен в c1
почему "Магические"?
Решение задачи поставленной в конце видео: использовать менеджер контекста width? Или это неправильный путь?
не совсем понятно для чего with?
@@selfedu_rus да, глупость я сморозил. Скорее, опять нужна какая-то магия.
Вроде получилось colab.research.google.com/drive/1M4Cdv82HeVBcQE-kn-zTRzjIWFNbrjpt?usp=sharing
попытался превратить магию методов в вуду дескрипторов и замыканий
class Desc:
def __init__(self):
self.opdct={'add':'+','sub':'-','mul':'*','truediv':'/'}
def __set_name__(self,owner,name):
name=name.strip('_')
self.prefix=name[0] if name.startswith(('i','r')) else None
self.name=name[1:] if self.prefix else name
def __get__(self,obj,owner):
def wrapp(other):
if type(other) in (int,float):
n1,n2=obj.num,other
else:
n1,n2=obj.num,other.num
if not self.prefix:
return owner(self.op(self.opdct[self.name],n1,n2))
if self.prefix=='r':
return owner(self.op(self.opdct[self.name],n2,n1))
if self.prefix=='i':
setattr(obj,'num',self.op(self.opdct[self.name],n1,n2))
return obj
return wrapp
@staticmethod
def op(opsign,n1,n2):
return eval(f'{n1}{opsign}{n2}')
class A:
__add__=Desc()
__sub__=Desc()
__mul__=Desc()
__truediv__=Desc()
__radd__=Desc()
__rsub__=Desc()
__rmul__=Desc()
__rtruediv__=Desc()
__iadd__=Desc()
__isub__=Desc()
__imul__=Desc()
__itruediv__=Desc()
def __init__(self,num):
но это все самая первая проба....развитие и улучшение в моих решениях на степике
суть метода использование non-data дескрипторов и замыканий в __get__
типовая конструкция такая
def __get__(self,object,owner):
def inner(other):
return ...
return inner
а для работы с методами такая еще конструкция return owner(getattr(object.param,self.name)(other.param))-если нам например нужно получить результат арифметической операции над объектом как новый объект с параметром -результатом операции над 2 объектами
КОРОЧЕ ПАСАНЫ ,,,,Я ЭТИ ВАШИ МАГМЕТОДЫ НА ДЕСКРИПТОРЕ ВЕРТЕЛ КАК ХОТЕЛ))))))
а зачем мы в качестве возвращаемого значения метода __add__ устанавливаем НОВЫЙ объект класса Clock с увеличенным количеством секунд:
return Clock(self.seconds + other)
вместо того, чтобы просто увеличить значение для текущего объекта:
return self.seconds + other
?
или это просто демонстрация того, как можно манипулировать данными, а нам на практике уже нужно будет действовать на своё усмотрение в зависимости от ситуации?
@@johnmazepa если нужно несколько слагаемых, например: a+b+c+d
В данной реализации вернется количество секунд, а не экземпляр класса Clock. Если требуется изменить атрибут конкретного класса и вернуть экземпляр этого же класса (без создания нового), предлагаю примерно такой вариант:
def __add__(self, other):
if not isinstance(other, (int, Clock)):
raise ArithmeticError("Атрибут должен быть целым числом (int) и объектом класса Clock")
if isinstance(other, int):
self._seconds += other
if isinstance(other, Clock):
self._seconds += other._seconds
return self
При попытке реализовать def __rsub__(self, other):
return self - other
он вычитает из объекта число, хотя хотелось бы наоборот. Как?
Решил проблему простой заменой знака результата. Это нормально, или как-то ещё делают?
правильно я понимаю, что метод __add__ он более общий по сравнению с radd и iadd?
не думай о секундах свысока.
Меня месяц тревожит вопрос, проиллюстрирую на примере из видео:
c1 = Clock(1000)
c1 = c1 + 100
Как сделать, чтобы работало и c1 = 100 + c1 ? Заранее спасибо)
__radd__
Подскажите пожалуйста зачем нужен return self в конце метода __iadd__. буду благодарен
Чтобы результат можно было присвоить какой-либо другой переменной (ссылку на объект). Если этого не требуется, то можно не возвращать (вроде бы).
Можно ли в iadd не делать return self, ведь на уже изменили значение атрибута в экземпляре, или будет ошибка?
нужно, т.к. там идет присваивание результата операнду слева от оператора +=
Спасибо за ответ
@@selfedu_rus вряд-ли увижу ответ, но попытаюсь)
В __radd__ мы получаем self и изменяя экземпляр через него, по сути методом и запишем значение, или я не прав? self ведь получается берет как раз что нам и нужно...
Все таки проверил и даже вроде осознал return нужен! ))
Как понять незначищий ноль справа , а потом говорите если 1 2 или то будет 0 1 0 2 и тд., где логика? Там помойму число больше нуля вправа едет а нули влево
Да ладно к словам придираться, главное суть же объяснили и код правильно работает. rjust делает выравнивание с право(right).
а __sum__
Материал и подача хорошие, но английский плох. Рекомендую подучить произношение слов, которые используются в программировании. Неправильное произношение вызывает недоумение у незнающих людей. Ещё было бы полезно объяснять полное название методов, например mul - multiply(умножать).
В данном видео метод __add__ читается как "эд" (добавить)
Не мог проще пример взять?!!!! Кучу воды наговорил, пока до маг. метода добрался.
опять бессмысленное усложнение объяснения на ровном месте, с этими секундами, сначала надо понять суть на примитиве, потом рассчитывать космический корабль, часть ресурсов мозга уходит что бы вникнуть ещё и в пример ...