Каждый программист, использующий Python, наверняка сталкивался с операторами, такими как сложение (+
) и умножение (*
). Но что происходит "под капотом" этих операторов? В этой статье мы погрузимся в детали их работы на примере базовых типов, пользовательских классов и строк.
Специальные методы: Сердце операторов
В Python операторы реализованы с помощью "специальных методов". Эти методы вызываются автоматически при использовании операторов.
Для сложения:
__add__(self, other)
__radd__(self, other)
Для умножения:
__mul__(self, other)
__rmul__(self, other)
Как Python складывает и умножает разные типы
Целые числа и числа с плавающей запятой:
При сложении int
и float
, например, 5
(int) и 3.5
(float), Python вызывает __add__
у объекта 5
с объектом 3.5
в качестве аргумента. Если сложение между этими типами невозможно напрямую, Python пытается использовать __radd__
у второго объекта.
Строки:
Строки в Python также поддерживают операторы +
и *
. С помощью +
можно объединить две строки:
s1 = "Hello"
s2 = "World"
result = s1 + s2 # "HelloWorld"
А с помощью *
можно "умножить" строку, повторив ее определенное количество раз:
s = "Hello"
result = s * 3 # "HelloHelloHello"
Производительность и реализация на C
За красивой и понятной высокоуровневой абстракцией Python скрывается низкоуровневая реализация на языке C. Большинство базовых операций, таких как сложение или умножение, оптимизированы и выполняются на "родном" уровне, что обеспечивает высокую скорость выполнения.
Когда вы используете базовые операторы с встроенными типами данных, такими как числа или строки, ваши операции выполняются на скорости, сравнимой со скоростью низкоуровневых языков программирования, таких как C.
Заключение
Python предлагает нам интуитивно понятные и мощные инструменты для работы с различными типами данных. Однако за этой простотой скрывается сложная архитектура, которая делает Python одновременно гибким и производительным. Благодаря низкоуровневой реализации на C, операции с данными в Python выполняются быстро, что делает этот язык отличным выбором для широкого круга задач.
Если вам понравилась эта статья или у вас есть дополнительные вопросы, пожалуйста, оставьте свой комментарий ниже!
Комментарии (24)
DustCn
09.10.2023 21:44+1>> Если сложение между этими типами невозможно напрямую, Python пытается использовать
__radd__
у второго объекта.И чем radd сильнее add, и почему только у второго обьекта?
lorc
09.10.2023 21:44+12Потому что это "reverse add". Абстрактный пример: мы складываем число и строку. Число не знает как складываться со строкой, поэтому
__add__
у первого объекта фейлится. Но возможно строка знает как складываться с числом? Поэтому вызывается__radd__
у второго объекта. Почему не вызвать__add__
у второго объекта? Потому что в Пайтоне операция сложения не коммутативна. A + B в общем случае не то же самое, что B + A. Поэтому второй объект должен знать что его операнд слева, а не справа. В этом и разница между__add__
и__radd__
Другой вопрос - почему автор не погрузился настолько глубоко, чтобы описать такие нюансы?
AWRDev
09.10.2023 21:44+5"Глубокое погружение" наверное даже подразумевало вхождение в дебри реализации на С (например, про длинную арифметику), но никак не "Операторы реализованы с помощью соответствующих специальных функций". Ещё бы можно было написать почему операторов инкремента и декремента нет.
yeswell
09.10.2023 21:44+5Попробуйте дополнить статью объяснением логики интерпретатора при вызовах
__add__
и__radd__
, как это сделали в комментариях вышеМожно ещё добавить пример того, как это можно использовать для объединения объектов
celen
09.10.2023 21:44+1Уважаемый, "глубокое" погружение - это когда вы детально расписываете, как именно происходит низкоуровневая оптимизация сложения на Си. А то, что вы написали, знает любой грамотный джун-питонист.
MihaTeam
09.10.2023 21:44Тут недавно статья была про программирование за "гранью", где взяли рандомные куски кода на js и python, про которые даже стажер знать должен.
Теперь глубокое погружение в + и * , когда про умножение и складывание строк рассказывают при первом изучении этих операторов, а про магические методы складывания и умножения узнают сразу, когда начинают изучать магические методы.Глубокое погружение возможно было бы при рассказе про переполнения на python, а точнее его отсутствия в привычном виде и почему это так, или почему оно все же возможно при работе с pandas или numpy или при работе с float к примеру.
Глубокое погружение возможно было бы при рассказе о том как на самом деле складываются строки и какая алокация при этом происходит и почему складывание большого количества строк по средствам цикла не очень хорошая идея.
Ну или на крайний случай хотя бы пример какой-нибудь реализации магических методов. К примеру класс вектора реализующий метод складывания векторов. Уже было бы значительно полезнее. А так мы имеем, что новичок который прочитает статью вспомнит разве что про складывание строк, про которое он уже скорее всего знал, а про магические методы так ничего и не поймет. Тому же, кто знает про магические методы ваша статья тем более ничего не даст.
fenrir1121
09.10.2023 21:44+1складывание большого количества строк по средствам цикла не очень хорошая идея.
Насколько мне известно CPython сам оптимизирует этот кейс.
code1 = """ from loremipsum import get_sentences l = get_sentences(99999) out = '' for line in l: out += word print(out) """ %timeit code1 # 11.9 ns ± 0.263 ns per loop (mean ± std. dev. of 7 runs, 100,000,000 loops each) code2 = """ from loremipsum import get_sentences l = get_sentences(99999) out = ''.join(l) print(out) """ %timeit code2 # 11.8 ns ± 0.0677 ns per loop (mean ± std. dev. of 7 runs, 100,000,000 loops each)
MihaTeam
09.10.2023 21:44Извиняюсь, этого момента не знал, спасибо, что поправили. Хотя лично я предпочитаю не доверять интерпретаторам и компиляторам на 100%, так что знать про то как оно работает внутри в любом случае полезно. Ну и плюс интересно, как себя поведет cpython при более комплексной логике построения строки. В любом случае, в одной это ветке больше глубины погружения, чем во всей статье выше.
Andrey_Solomatin
09.10.2023 21:44Погружение было бы еще глубже, если бы была упомянута роль
NotImplemented
в этих функциях.
ShashkovS
09.10.2023 21:44Чтобы на этой странице было побольше полезно, то приведу два примера как быстро считать числа Фибоначчи при помощи матриц 2×2 и чисел вида a+b√5.
Мы реализуем класс матриц с операцией умножения и возведения в степень.
И класс чисел вида a+b√5 с основной арифметикой.По формуле Бине $$F_n = \dfrac{ \frac{1+\sqrt5}{2}^n - \frac{1-\sqrt5}{2}^n }{\sqrt{5}}$$
А с матрицами Фибоначчи лезет при возведении матрицы (1 1) (1 0) в степень. А в степень можно возводить быстро.class Mat2x2: __slots__ = ['a', 'b', 'c', 'd'] def __init__(self, a, b, c, d): self.a = a self.b = b self.c = c self.d = d def __matmul__(x, y): ans = x.copy() ans @= y return ans def __imatmul__(x, y): x.a, x.b, x.c, x.d = x.a * y.a + x.b * y.c, x.a * y.b + x.b * y.d, x.c * y.a + x.d * y.c, x.c * y.b + x.d * y.d return x def __pow__(self, exp): cur = Mat2x2(1, 0, 0, 1) base = self.copy() while exp: if exp & 1: exp -= 1 cur @= base else: exp >>= 1 base @= base return cur def copy(self): return Mat2x2(self.a, self.b, self.c, self.d) def __repr__(self): return f'{self.__class__.__name__}({self.a}, {self.b}, {self.c}, {self.d})' class R5: def __init__(self, a=0, b=0): self.a = a self.b = b def __repr__(self): return f'R5({self.a}, {self.b})' def __str__(self): if self.a and self.b: return f'({self.a}{self.b:+}√5)' elif self.b: return f'{self.b}√5' else: return f'{self.a}' def __add__(x, y): return R5(x.a + y.a, x.b + y.b) def __sub__(x, y): return R5(x.a - y.a, x.b - y.b) def __mul__(x, y): return R5(x.a * y.a + x.b * y.b * 5, x.a * y.b + x.b * y.a) def __pow__(x, power): if power == 0: return R5(1, 0) elif power % 2 == 1: return x * (x ** (power - 1)) else: sq = x ** (power // 2) return sq * sq def __floordiv__(x, n): return R5(x.a // n, x.b // n) def fib_stupid(n): c, p = 0, 1 for _ in range(n): c, p = c + p, c return c def fib_bine(n): return (R5(1, 1) ** n - R5(1, -1) ** n).b // 2 ** n def fib_matrix(n, *, fib_mat=Mat2x2(0, 1, 1, 1)): if n == 0: return 0 fib_pow = fib_mat ** (n - 1) return fib_pow.d # Проверка корректности for i in range(0, 100): assert fib_bine(i) == fib_stupid(i) == fib_matrix(i) print(fib_matrix(100))
Irish_head
09.10.2023 21:44Если "глубокое" погружение то и про __iadd__ пару слов бы сказали. И как такое может работать с неизменяемыми объектами.
fireSparrow
У меня вопрос — а где, собственно, обещанное в заголовке "глубокое погружение" ?
vandriichuk Автор
я извиняюсь. я так понял, для Вас это легкая тема. это круто. работаю с Python 5 лет и только недавно вообще в принципе задумался как работают эти операторы и разобрал эту тему. мне показалось, что есть такие же вокруг программисты. эта заметка написана для нас.
omaxx
ждем статью "Глубокое погружение в магию '-' и '/'" от создателя "Глубокое погружение в магию '+' и '*'"
vandriichuk Автор
из-за таких токсичных людей Хабр превращается в токсичную клоаку старых снобов.
alexeydg
почему превращается? он всегда таким был) а статья на самом деле провальная
kuza2000
Да нет, тут не токсичность)
Просто ожидания от сттатьи по заголовку не оправдалось. "Глубокое" для хабра - это несколько поглубже) Куда-нибудь на уровень ассемблера, устройства объектов в памяти, нюансов работы сборщика или хотя бы тонкости, поиск которых нетривиален. А тут для большинства нет ничего нового и даже интересного. А кто это не знал - то цена вопроса 1 минута поиска в доке.
В общем, цена информации из статьи - околонулевая.
Не стоит воспринимать как негатив, просто делайте выводы. Надеюсь, следующая статья будет намного интереснее и встречена тепло! Успехов! )
kekoz
За 5 лет?! Парень, постарайся понять меня правильно: использовать язык, который от рождения поддерживает ООП-парадигму, и лишь спустя годы узнать основы того, как же в языке реализуются операции над объектами — ну..., по меньшей мере, странно...
И да, что-либо магическое в программистской рутине видят только космически далёкие от программирования люди. На форуме, где обитают программисты, использование слова “магия” и подобных в заголовках статей о рутине — неработающий способ привлечения аудитории.
Skykharkov
Вот да... Как в анекдоте. Старый, но не грех и процитировать.
Мужик. Фанат бокса. Сегодня - бой за звание чемпиона мира. За час - отпрашивается с работы. По дороге забегает в магазин. Покупает креветки и пиво.40 минут до матча - он дома. Бросает креветки в воду, пиво - в морозилку, чтобы быстрее.5 минут до матча. Достает пиво, вынимает креветки. Минута до матча. Мужик сидит перед телевизором, в левой руке - очищенная креветка, в правой руке - открытая бутылка. И... Гонг! Первый раунд, первый удар... Нокаут. Мужик сидит, сказать ничего не может. Только глазами лупает, да руками в воздухе махает. Оборачивается к двери в комнату, а там его жена стоит, сложив руки на груди, и спрашивает его:- Ну?! Теперь ты меня понимаешь?
taskevich
Согласен, нету команд add, mul, sub, div, которые выполняются на самом дне