Сегодня рассказываем о самом странном операторе Python — операторе моржа. Для чего он нужен, и как использовать его с учётом других особенностей языка? Подробности к старту курса по Fullstack-разработке на Python — под катом:
- Основы
- Накапливание (аккумулирование) данных на месте
- Именование значений внутри f-строки
- Any и All
- Подводные камни и ограничения
- Заключительные мысли
Оператор присваивания (или оператор моржа) появился ещё в Python 3.8, но он всё ещё вызывает споры, а многие необоснованно его ненавидят. Я же попытаюсь убедить вас, что этот оператор — хорошее дополнение к языку, а при правильном применении он может помочь сделать код короче и яснее.
Основы
Посмотрим на основные варианты применения оператора моржа, которые могут убедить вас попробовать, если вы ещё не знакомы с
:=
.В первом примере, который я хочу показать, оператор моржа сокращает количество вызовов функций.
Представим функцию
func()
, которая выполняет очень ресурсоёмкие вычисления. Её работа занимает много времени, поэтому вызывать функцию много раз не хочется:# "func" вызывается три раза
result = [func(x), func(x)**2, func(x)**3]
# Используем результат "func" в одну строку, без многострочного кода
result = [y := func(x), y**2, y**3]
В объявлении первого списка выше
func(x)
вызывается трижды. Каждый раз возвращается один и тот же результат — и это пустая трата времени ресурсов. При перезаписи с оператором моржа func()
вызывается только один раз: её результат присваивается y
и повторно используется для оставшихся значений списка. Вы можете возразить: «Морж не нужен, я могу просто добавить y = func(x)
перед объявлением списка!» Да, но это лишняя строка кода, и на первый взгляд — не зная, что func(x)
работает очень медленно, — может быть непонятно, для чего нужна переменная y
.Если это не убедило вас, есть ещё кое-что. Вот списковое включение с той же дорогостоящей
func()
:result = [func(x) for x in data if func(x)]
result = [y for x in data if (y := func(x))]
В первой строке функция
func(x)
в каждом цикле вызывается дважды. С оператором моржа функция вычисляется один раз, внутри if
, а затем результат используется повторно. Длина кода одинаковая, обе строки одинаково читаемы, но вторая в два раза эффективнее. Производительность можно сохранить, заменив оператор моржа на полный цикл for
, но для этого потребуется 5 строк кода.Один из самых распространённых вариантов применения оператора моржа — сокращение вложенных условий, например в сопоставлении при работе с регулярными выражениями:
import re
test = "Something to match"
pattern1 = r"^.*(thing).*"
pattern2 = r"^.*(not present).*"
m = re.match(pattern1, test)
if m:
print(f"Matched the 1st pattern: {m.group(1)}")
else:
m = re.match(pattern2, test)
if m:
print(f"Matched the 2nd pattern: {m.group(1)}")
# ---------------------
# Чище
if m := (re.match(pattern1, test)):
print(f"Matched 1st pattern: '{m.group(1)}'")
elif m := (re.match(pattern2, test)):
print(f"Matched 2nd pattern: '{m.group(1)}'")
Код сократился с 7 до 4 строк, а за счёт удаления вложенного
if
стал более читаемым.Следующая в списке — идиома «loop-and-half» («цикл пополам»):
while True: # Цикл
command = input("> ")
if command == 'exit': # и пополам
break
print("Your command was:", command)
# ---------------------
# Чище
while (command := input("> ")) != "exit":
print("Your command was:", command)
Обычное решение — фиктивный бесконечный цикл
while
, в котором поток управления передаётся оператору break
. Но также можно задействовать оператор моржа, чтобы переназначить значение command
, а затем использовать его в условном цикле while
в той же строке. Это сделает код намного чище и короче.Аналогичное упрощение применимо и к другим циклам
while
, например при чтении файлов построчно или при получении данных из сокета.Накапливание (аккумулирование) данных на месте
Перейдём к более сложным случаям применения
оператора моржа
:data = [5, 4, 3, 2]
c = 0; print([(c := c + x) for x in data]) # c = 14
# [5, 9, 12, 14]
from itertools import accumulate
print(list(accumulate(data)))
# ---------------------
data = [5, 4, 3, 2]
print(list(accumulate(data, lambda a, b: a*b)))
# [5, 20, 60, 120]
a = 1; print([(a := a*b) for b in data])
# [5, 20, 60, 120]
Первые две строки показывают, как использовать
:=
для вычисления промежуточного результата. В настолько простом случае лучше подойдёт функция itertools
, например accumulate
, как в двух следующих строках. Но в ситуациях сложнее itertools
довольно быстро становится нечитаемым, и, на мой взгляд, версия с :=
намного лучше, чем с lambda
.Если вы всё ещё не уверены, ознакомьтесь с нечитабельными примерами в документации
accumulate
(с накоплением процентов или логистическим отображением). Попробуйте переписать их с оператором моржа, они станут выглядеть намного лучше.
Именно так, шаг за шагом, вы станете востребованным профессионалом в области IT:
Именование значений внутри f-строки
Этот пример показывает возможности и ограничения
:=
, но не лучшие практики.Если очень хочется,
:=
можно использовать внутри f-строк:from datetime import datetime
print(f"Today is: {(today:=datetime.today()):%Y-%m-%d}, which is {today:%A}")
# Today is: 2022-07-01, which is Friday
from math import radians, sin, cos
angle = 60
print(f'{angle=}\N{degree sign} {(theta := radians(angle)) =: .2f}, {sin(theta) =: .2f}, {cos(theta) =: .2f}')
# angle=60° (theta := radians(angle)) = 1.05, sin(theta) = 0.87, cos(theta) = 0.50
В первом
print
оператор :=
определяет переменную today
, которая затем используется в той же строке; today
избавляет нас от повторного вызова datetime.today()
.Точно так же во втором примере объявляется переменная
theta
, которая повторно используется для вычисления sin(theta)
и cos(theta)
. В этом случае мы также используем его в сочетании с оператором «обратный морж» — на самом деле это просто =
, с ним выражение выводится рядом с его значением, а :
используется для форматирования выражения.Обратите внимание: чтобы f-строка правильно интерпретировала выражения с оператором моржа, их нужно заключать в круглые скобки.
Any и All
Чтобы проверить, удовлетворяют ли какие-то значения в некотором итерируемом объекте определённому условию, можно восопользоваться функциями
any()
и all()
. Но что делать, если нужно зафиксировать значение, из-за которого any()
вернуло True
(так называемое «свидетельство»), или значение, которое привело к сбою all()
— «контрпример»?numbers = [1, 4, 6, 2, 12, 4, 15]
# Возвращает только логические значения, а не обычные
print(any(number > 10 for number in numbers)) # True
print(all(number < 10 for number in numbers)) # False
# ---------------------
any((value := number) > 10 for number in numbers) # True
print(value) # 12
all((counter_example := number) < 10 for number in numbers) # False
print(counter_example) # 12
И
any()
, и all()
для вычисления выражения используют вычисление по короткой схеме, то есть прекращают вычисления, как только находят первое «свидетельство» или «контрпример» соответственно. А значит, при таком трюке созданная оператором моржа переменная всегда будет возвращать первого «свидетеля» или «контрпример».Подводные камни и ограничения
Хотя выше я пытался убедить использовать оператор моржа, думаю, также важно предупредить о некоторых его недостатках и ограничениях. Ниже — подводные камни, с которыми вы можете столкнуться.
В предыдущем примере вы видели, что вычисление по короткой схеме полезно для захвата значений в
any()
/all()
, но иногда это может привести к неожиданным результатам:for i in range(1, 100):
if (two := i % 2 == 0) and (three := i % 3 == 0):
print(f"{i} is divisible by 6.")
elif two:
print(f"{i} is divisible by 2.")
elif three:
print(f"{i} is divisible by 3.")
# NameError: name 'three' is not defined [имя 'three' не определено]
Мы создали условие с двумя соединёнными
and
присваиваниями. Эти and
проверяют, делится ли число на 2, 3 или 6 в зависимости от того, выполняются ли первое, второе или оба условия. На первый взгляд это может показаться хорошим приёмом, но из-за вычисления по короткой схеме, если выражение (two: = i % 2 == 0)
не выполняется, вторая часть будет пропущена, а значит, имя three
окажется неопределённым или будет иметь устаревшее значение из предыдущего цикла.Вычисление по короткой схеме может быть и преднамеренным. Использовать его можно с регулярными выражениями, чтобы найти в строке несколько паттернов:
import re
tests = ["Something to match", "Second one is present"]
pattern1 = r"^.*(thing).*"
pattern2 = r"^.*(present).*"
for test in tests:
m = re.match(pattern1, test)
if m:
print(f"Matched the 1st pattern: {m.group(1)}")
else:
m = re.match(pattern2, test)
if m:
print(f"Matched the 2nd pattern: {m.group(1)}")
# Сопоставлено с первым паттерном: thing
# Сопоставлено со вторым паттерном: present
for test in tests:
if m := (re.match(pattern1, test) or re.match(pattern2, test)):
print(f"Matched: '{m.group(1)}'")
# Сопоставлено: 'thing'
# Сопоставлено: 'present'
Мы уже видели версию этого фрагмента в первом разделе статьи, где в сочетании с оператором моржа использовались
if
/elif
. Здесь код упрощается ещё больше: условное выражение сокращается до единственного if
.Если вы только знакомитесь с оператором моржа, то заметите, что он заставляет переменные области видимости вести себя по-разному при списковом включении:
values = [3, 5, 2, 6, 12, 7, 15]
tmp = "unmodified"
dummy = [tmp for tmp in values]
print(tmp) # Как и ожидалось, "tmp" не разбился на элементы. Он по-прежему "немодифицированный"
total = 0
partial_sums = [total := total + v for v in values]
print(total) # Выводит: 50
При обычном списковом включении
list
, dict
или set
переменная цикла не просачивается в окружающую область, и поэтому любые существующие переменные с тем же именем не изменятся. Но с оператором моржа переменная из включения (total
в приведённом выше коде) останется доступной после завершения включения, она примет значение из внутреннего включения.Когда вы освоитесь с оператором моржа, то сможете попробовать его в других случаях. Но вот одно место, где вы никогда не должны его использовать, — это оператор
with
:class ContextManager:
def __enter__(self):
print("Entering the context...")
def __exit__(self, exc_type, exc_val, exc_tb):
print("Leaving the context...")
with ContextManager() as context:
print(context) # None
with (context := ContextManager()):
print(context) # <__main__.ContextManager object at 0x7fb551cdb9d0>
С обычным синтаксисом
with ContextManager() as context: ...
context
привязывается к возвращаемому значению context.__enter__()
, а если вы используете версию с :=
— к результату ContextManager()
. Часто это не имеет большого значения, ведь context.__enter__()
обычно возвращает self
, но если это не так, то при отладке возникнут большие проблемы.Вот что происходит, когда вы используете оператор моржа с менеджером контекста
closing
:from contextlib import closing
from urllib.request import urlopen
with closing(urlopen('https://www.python.org')) as page:
for line in page:
print(line) # Вывод HTML-кода страницы
with (page := closing(urlopen('https://www.python.org'))):
for line in page:
print(line) # TypeError: 'closing' object is not iterable [объект 'closing' не итерируемый]
Ещё одна проблема, с которой вы можете столкнуться, — приоритет оператора моржа, ведь он ниже приоритета логических операторов:
text = "Something to match."
flag = True
if match := re.match(r"^.*(thing).*", text) and flag:
print(match.groups()) # AttributeError: 'bool' object has no attribute 'group' [у объекта 'bool' нет атрибута 'group']
if (match := re.match(r"^.*(thing).*", text)) and flag:
print(match.groups()) # ('thing',)
Здесь мы видим, что присваивание нужно заключить в круглые скобки, чтобы гарантировать, что результат
re.match(...)
присваивается переменной. Если этого не сделать, выражение and
будет вычисляться первым, то есть будет присвоен логический результат.И, наконец, не ловушка, а скорее небольшое ограничение. Сейчас с оператором моржа нельзя использовать подсказки встроенного типа. Поэтому, если захочется указать тип переменной, нужно разбить её на 2 строки:
from typing import Optional
value: Optional[int] = None
while value := some_func():
... # Что-то делаем
Заключительные мысли
Оператором моржа, как и любой другой особенностью синтаксиса, можно злоупотребить: снизить ясность и читабельность кода. Не нужно писать его везде, где только возможно. Относитесь к этому оператору как к инструменту — знайте его преимущества и недостатки, а используйте там, где это уместно.
Если хочется увидеть более практичное и эффективное использование оператора моржа, узнайте, как он появился в стандартной библиотеке CPython — все эти изменения можно найти в этом PR. И рекомендую прочитать PEP 572, в котором есть обоснование появления оператора и ещё больше примеров.
А мы поможем прокачать ваши навыки или с самого начала освоить профессию, востребованную в любое время:
Комментарии (43)
zorn_v
19.08.2022 00:42+5Ну незнаю почему он в питоне кажется "странным", в пхп/с/else такие вещи кажутся обычным делом
```
if ($some = $obj->getSome()) {
// doing something with some
}
```
Ну или на крайняк
```
if (null !== $some = $obj->getSome()) {
// doing something with some
}
```
Потому что = очень похоже на == но тебе об этом никто не скажет )
sedyh
19.08.2022 01:34+2Python превращается в Go
vladimirad
19.08.2022 14:38+3Что все уже забыли великий и ужасный паскаль? Присваивания. Оператор присваивания. (рассуждения про кальку английского валруса вырезаны внутренней цензурой)
Great_Beaver
19.08.2022 06:17+12Сразу вспомнился баш:
xxx: этот язык, хоть и от гугла, никогда не станет успешным, помню о Правиле.
yyy: каком?
xxx: Ни один язык с ':=' никогда не достигнет успеха.
xxx: Это оператор смерти.
koreychenko
19.08.2022 09:06+4Я понимаю, что еще нет сложившейся практики перевода этого walrus "моржеприсваивания" на русский язык. Но "оператор моржа" это "ту мач ас фор ми". Вспоминаются гуртовщики мыши (для тех, кто понимает).
stranger777
20.08.2022 22:39Дмитрий Юрьевич Фёдоров, преподаватель программирования и автор вот этой книги о программировании на Python (рекомендовано МинОбр, рецензировалась докторами технических наук), с вами не согласится. Именно в этой книге вы найдёте "оператор моржа".
Прямо на Хабре по соответствующему запросу был (и есть) вариант "моржовый оператор", что звучит куда более комично, чем "оператор моржа", и даже может вызвать нежелательные ассоциации с известным выражением. :)
Скорее всего, позже в русском языке устоится перевод морж. Просто морж, как самый краткий (здесь уже есть пример). А пока — даже судя по реакции здесь — люди не очень свыклись с самим оператором моржа как таковым, часто воспринимают его как нечто чужеродное, и многим людям его название трудно сократить чисто психологически.
Да, как верно заметили, был и есть Pascal, но там этот оператор имеет чётко определённое значение простого присваивания. И, судя по всему, слово оператор пока остаётся у моржа, чтобы подчеркнуть, что это не опечатка (и не что-то, как я уже говорил, чужеродное), а именно оператор.
Оператор-морж тоже звучит гораздо более странно, ведь мы не называем < или > оператором-птичкой, а = — оператором-рельсами. :) Оператор моржа среди всех вариантов хотя бы обладает оттенком нарицательного. Ну и всё же изначально, когда этому оператору давали название, лёгкую комичность в него закладывали. Она выражается и в переводе.
P.S. А в книгу, на которую ссылаюсь, автор явно вкладывал душу. И видно по тексту, что своё дело он любит, так что "оператор моржа" там появился явно не оттого, что автор "писал как попало" :)
Shrim
19.08.2022 09:31+11Не надо моржа пихать где не попадя, во многих примерах он не нужен.
Не надо экономить строки в ущерб читаемости.
О ужас, аж две лишних строки кода!# result = [y := func(x), y**2, y**3] y = func(x) z = 3 # or 100500 result = [y**e for e in range(1, z+1)]
Зато код легко читаем и изменяем, при изменении количества степеней достаточно изменить значение переменной z
Не потребовалось 5 строк кода, код стал даже короче на 2 символа.# result = [y for x in data if (y := func(x))] result = [x for x in map(func, data) if x]
И снова код более читаем и гибок.import re test = "Something to match" patterns = ( r"^.*(thing).*", r"^.*(not present).*", ) for i, pattern in enumerate(patterns, 1): m = re.match(pattern, test) if m: break print(f"Matched the {i}st pattern: {m.group(1)}")
В случае добавления паттерна в этом варианте добавится всего одна строка, в приведённом в статье — три строки.
Иногда нужно читать документацию, чтоб не выдумывать костыли и велосипеды.# print(list(accumulate(data, lambda a, b: a*b))) print(list(accumulate(data, operator.mul)))
Но я не говорю, что морж не нужен.
Это инструмент и использовать его надо с умом и по назначению.
Например в примере с Any и All он вполне уместен.any((value := number) > 10 for number in numbers) # True print(value) # 12
lgorSL
19.08.2022 11:04+2Например в примере с Any и All он вполне уместен.
Мне кажется, нет, либо пример надо менять. Во-первых, если список пустой, к value вообще нельзя нельзя обращаться в дальнейшем - оно не определено.
Во-вторых, если в списке не будет подходящего объекта, value всё равно будет что-то содержать, и надо бы проверять, что вернули функции all или any.Shrim
19.08.2022 11:58-1То что сам пример упрощён не говорит о том, что морж в нём не уместен.
Fix:result = any( number > 10 for i, number in enumerate(numbers) if (key := i) is not None # проверка не нужна, просто сохраняем индекс ) if numbers and result: print(f"key:\t{key}\r\nvalue:\t{numbers[key]}")
mayorovp
19.08.2022 13:03+11Это не код, а ребус, не надо так делать.
Добавление побочных эффектов в функциональные конструкции усложняет их понимание больше чем любые "моржеоператоры" в примерах выше.
Shrim
19.08.2022 13:30+2Кто же спорит.
Лично я в данном случае не стал бы использовать функции Any и All, а написал что-то вроде:for i, number in enumerate(numbers): if number > 10: print(f"{number} > 10 (index {i})") break else: print("all numbers < 10")
mayorovp
19.08.2022 11:42result = [x for x in map(func, data) if x]
Не потребовалось 5 строк кода, код стал даже короче на 2 символа.Ага, только теперь тут комбинируются два разных способа обращения с последовательностями.
Надо или оставаться на чистом List Comprehension, и тут "оператор моржа" поможет — или отказываться от List Comprehension полностью, и вызов map вложить внутрь filter.
Shrim
19.08.2022 12:05+2А что плохого в комбинировании? Можете сослаться на какой-нибудь PEP?
result = [x for x in (func(y) for y in data) if x]
mayorovp
19.08.2022 12:50-3Чтение List Comprehension и чтение конструкций из функций высшего порядка — это два разных навыка, а вы требуете от читающего код обладать ими одновременно.
rnb
19.08.2022 16:34>> код стал даже короче на 2 символа
Тот случай, когда 1 старый меч лучше двух новых граблей ))
dmitrysvd
20.08.2022 06:06Использовать переменные цикла за пределами цикла, по моему, довольно странно и неявно. И как уже сказали, если не сработал break, все равно будет напечатано значение
ddastt
20.08.2022 22:13-1Читабельность безусловно становится лучше, но ваш вариант с map заставит функцию выполниться несколько раз, в то время как "морж"-вариант ее результат позволяет переиспользовать:
def test(n = 3):
... print('test :', n)
... return n + 7
...
y = [x := test(), x2, x3]
test : 3
y = [test()**e for e in (1, 2, 3)]
test : 3
test : 3
test : 3
danilovmy
19.08.2022 09:34+14Каждый раз, когда я читаю что-то от Мартина Хайнца, я понимаю - Мартин знает много, но не больше. Он копает достаточно, но не глубоко.
На Python DE 2022 я рассказывал о моржовом операторе с тем же примером [xx := yy, xx2, xx3].
Но в моем докладе - это был пример, почему мы не должны писать такой код. Все примеры в этой статье Мартина - это шаблоны кода, которые я бы не стал использовать. Кстати, и не только в этой статье у него такое встречается.Я согласен, с О-моржовым у нас становится меньше строк кода, но зато в этой части кода невероятно возрастает сложность. Каждая строка начинает делать более чем один 1 тип действий за раз. Каждая строка начинает люто нарушать п.2 из PEP20.
Кстати, Мартин забыл важный пример для статьи. Как с помощью walrus превратить список аргументов в функции в ключевые аргументы:
my_result = function_call(first_arg_in_args := xxx, second_arg_in_args:=yyy, ... ) # вместо function_call(xxx, yyy).
Здесь мы добавляем дополнительную информацию в вызов функции и избежали дополнительных комментариев, дав дополнительно понятные имена позиционным аргументам. И, опять, я не считаю, что это хороший пример кода python.
p.s. На if с присвоением в питоне сделан pattern matching. Но что-то Мартин как-то про это забыл.
thatsme
19.08.2022 11:01while (command := input("> ")) != "exit":
print("Your command was:", command)А мне наоборот, такой синтаксис нравится ... глядишь, лет ч-з десять ';' введут, и отступы уберут, и совсем питон на C станет похож ...
sukhe
19.08.2022 21:06Опоздали вы с пожеланиями:
>>> a=10; b=20; c=30; d = a*b*c; print (d)
6000
Но совсем похож не станет — фигурные скобочки уже заняты под словари/множества.thatsme
19.08.2022 23:04-1Да ладно. Их можно и под что-то другое использовать. Как пример bash, где можно фигурные скобочки использовать для совершенно разных вещей. Это контекстно зависимо. Я в своё время на 1-е апреля для bash патч сделал, для того что-бы then, do, fi, done, заменить фигурными скобками. Тогда как-раз была какая-то уязвимость с этим связана, уже не помню, но фигурных скобочек становилось очень много, что читабельность не уличшало, а безопасность кода просто в негатив выводило. Вот нашёл: https://github.com/pkpro/ebash
Bronx
19.08.2022 09:37+9Logical short-cirquit evaluation всё же лучше переводить как "вычисление по короткой схеме", потому что "замыканием" традиционно переводится термин "closure".
evgenyk
19.08.2022 11:27+11Мне кажется, если для иллюстрации достоинства какого-то оператора языка нужно изобретать притянутые за уши примеры, этот оператор не нужен и даже вреден.
N-Cube
19.08.2022 12:22-3Мало того, что польза оператора сомнительна, так еще и код с ним не будет работать, к примеру, на Google Colab (Python 3.7) и в целом на не самых новых системах Linux. Ради модного оператора сломать работающий код?
anisimovih
20.08.2022 22:41+1Вы сейчас реально придрались к фиче из-за того, что она появилась в более новой версии питона, чем та, которая нужна конкретно вам?
N-Cube
21.08.2022 06:58-1Все облачные среды и вообще линукс-юникс системы - это нужно конкретно мне, вы это серьезно? Вылезайте уже со своего локалхоста… Одно дело, что в язык какая-то фича добавлена (без громких анонсов от разработчиков), другое - пихать ее везде, где она и не нужна, чисто ради хайпа, делая код не переносимым.
mayorovp
21.08.2022 10:51+2Устаревшие дистрибутивы и облака — это проблема дистрибутивов, облаков и их пользователей, а никак не Питона.
С момента выхода Python 3.8 прошло почти три года! За такое время даже С++ новый стандарт выпускает, и никто не жалуется что они это делают слишком часто.
NeoCode
19.08.2022 13:06+1Проблема Питона не в операторе моржа, а в том что объявление переменных синтаксически не отличается от их использования. И вот этот оператор моржа, как я понял из статьи, пытается частично решить эту проблему... Но поскольку решение частичное, оно все равно не решает проблему. Вот если бы обязали объявлять все переменные только оператором моржа, а присваивать в ранее объявленные переменные - обычным присваиванием (как это сделано в Go), вот тогда бы был толк.
Michae_JSON
19.08.2022 14:28+2Ребят, я тут новенький, стоит учить моржа? Или дрессировке не поддается на начальном этампе?
danilovmy
19.08.2022 17:15+2Если планируешь выступать с моржом и питоном, то, по мне, пока цирк какой-то получается. :)
sukhe
19.08.2022 21:34+1Да что там сложного? Этот оператор просто сохраняет в переменную значение выражения для последующего использования. Для каких-нибудь однострочников вполне пойдёт:
def fib(n): return m if (m:=abs(n))<2 else int(fib(n-2*(z:=n/m)) + z*fib(n-z))
Ну да — можно расписать красиво на несколько строчек, с комментариями, аннотациями типов и даже юниттестами. Это если куда-нибудь в продакшен.
А если надо для себя, «на коленке» по быстрому чего-нибудь прикинуть — ну почему нет-то?
Prion
19.08.2022 23:55Автора видимо хватил бы инфаркт, если бы узнал как простые операции пишутся на. Ассемблере. Действительно не стоит впадать в крайности и в ущерб читаемости сокращать код.
orenty7
20.08.2022 23:03Имхо, просто переизобрели классику от K&R
while((c = getchar()) != EOF) {...}
Vindicar
Что я вынес из статьи: оператор моржа можно использовать только для сокращения условий.
Всё остальное — шуточка из разряда «вставить SQL-запрос в f-строку».