Comments 26
На мой взгляд, самый вопиющий речекряк питона - это существование StopIteration
А по-моему это гениально. Или как в других языках, будем иметь два метода вместо одного, типа Iterator.hasNext() -> bool
и Iterator.next()
.
После того, как я перешёл на Rust... ладно, не перешёл, но плотно изучил, меня каждый раз от этого StopIteration передёргивает.
В Rust (для сравнения) next возвращает Option<Value>, который либо Some(Value), либо None (None - это не питоновский None, это из enum'а None|Some). В рамках синтаксического сахара for
вычитывает из итератора пока не получит None. Но любой желающий может взять next() сам сколько нужно раз и посмотреть содержимое.
И правильно обработать None. В питоне "правильно обработать" можно только так:
try:
foo = next(bar)
except StopIteration:
foo = "something else"
Сравните это с rust'овой версией:
let foo = bar.next().unwrap_or("something else")
Передёргивает меня тут не от числа строк, а от того, что exception используется не для исключительной ситуации.
UDP задумался, есть ли дефолт у next. Есть, спасибо. Можно foo=next(bar, "something else")
, хотя моя претензия по поводу эксепшена в неисключительной ситуации всё равно остаётся.
Какая претензия может еще оставаться? Какое должно быть поведение по умолчанию если нет следующего элемента, а ты вызываешь next()? Почему именно ваше поведение по умолчанию лучше возбуждения StopIteration? В крайнем случае, реализуйте __iter__ и __next__ у класса объекта bar задав свое поведение при случае когда последовательность закончилась.
Что будет делать Rust если убрать .unwrap_or("something else")? Чем подход bar.next().unwrap_or("something else") лучше подхода next(bar, "something else")?
Тем, что вместо unwrap я могу написать так:
if let Some(x) = foo.next(){
...
}
Как вы такое на Python напишете?
Это преимущества не Rust, а функциональной парадигмы.
Pattern Matching до сих пор нет даже в Kotlin: ждут появления его в Java (оно задерживается). Бреслав где-то говорил, что "патмат" требует примерно столько же строк в реализации с нуля, сколько весь остальной язык.
Да пожалуйста, если не хотите StopIteration
:
class Sentinel: ...
if x := next(foo, Sentinel):
...
Только это не нормальный Питоний код же. Вы часто видели, чтобы next()
вызывался явным образом, а не в контексте for
? Или вам часто приходится кидать StopIteration
вручную?
Мне кажется, что вы не до конца рассмотрели идею со StopIteration
.
Это исключение появилось как часть протокола итераторов и генераторов. А генераторы можно соединять, даже не имея yield from
.
А как в цепочке генераторов проще всего сигнализировать на самый верх, что глубинный генератор истощён? Бросаем исключение, которое просто всплывает наверх.
И да, этот механизм - противоречивый и далеко не самый эффективный. Мне лично больше импонирует функциональный подход Раста. А с другой стороны - этот механизм чертовски простой и проверенный временем :)
Рассказываю простой пример, где надо next вызывать вручную.
У нас грок-подобный парсер потока строк. Мы хотим найти match1, после чего мы почти уверены, что будет match2 и match3. "Почти", потому что может оказаться, что нет, и тогда match1 тоже не валидный, и строки под ним тоже, потому что по спецификации, если match1, то match2 и match3 идут строго после.
def parse(iterator):
data = []
while True:
match1 = grok1.match(next(iterator))
if match1 == EOF: ## not valid
break
if match1:
match2 = grok2.match(next(iterator))
match3 = grok3.match(next(iterator))
if match1 and match2:
data.append(combine(match1, match2, match2))
yield from process(data)
А теперь сделайте так, пожалуйcта, чтобы не надо было это обтыкивать try/except и покажите пример красивой реализации break
.
У вас соль в том, что парсер и токенайзер жестко связаны друг с другом. EOF должен возвращаться токенайзером. Парсер же читает и обрабатывает поток токенов. Но даже если их по какой-либо причине нельзя разоединить, тут есть над чем поработать, например:
def parse(iterator):
data = (
combine(*matches)
for matches in read_triplets(iterator)
)
yield from process(data)
def read_triplets(iterator):
while all((
(match_1 := grok1.match(next(iterator, EOF))) != EOF,
(match_2 := grok2.match(next(iterator, EOF))) != EOF,
(match_3 := grok3.match(next(iterator, EOF))) != EOF,
)):
yield (match_1, match_2, match_3)
Конкретно в вашем случае, насколько я понял, окончание входящего потока автоматически означает что вы не найдёте какой-то из своих матчей, что означает отсутствие данных для обработки.
А значит, тут можно обойтись ОДНИМ try/except вокруг всего цикла. Что внезапно оказалось даже лучше паттерн-матчинга, которых потребовалось бы аж три.
Что будет делать Rust если убрать .unwrap_or("something else")?
Код не скомпилируется, ибо в foo
будет лежать значение типа Option<SomeType>
, а ниже по коду будет ожидаться foo
типа SomeType
.
Ничего не имею против ни Python, ни PHP, но картинку можно было бы подобрать более подходящую :)
Работаю с Python уже почти 10 лет, и на мой взгляд по количеству неочевидных подводных камней, он может легко сравниться с низкоуровневыми языками.
Классический пример, дефолтные параметры функций:
from time import time
def foo(timestamp=time()):
print(timestamp)
Все что связано с модулями и импортами это вообще отдельная история.
Классический пример, дефолтные параметры функций:
А можно разъяснить, что не так с дефолтными параметрами? КМК достаточно очевидно, зачем оно.
Дефолтные аргументы создаются один раз при первом обращении к функции. Например, если бы по-умолчанию подставлялся список, то он был бы всегда один и тот же между вызовами:
def foo(bar=[]):
return bar
x = foo()
x.append(4)
y = foo()
В результате x и y ссылаются на один и тот же список.
Про дефолтные значения в FAQ еще написано
https://docs.python.org/3/faq/programming.html#id13
Я вообще всем новичкам рекомендую FAQ читать, а потом еще перечитывать. А его к сожалению мало читают, а там как раз много полезного, чтобы поменьше на грабли наступать.
Да, на любую критику языка можно сказать "RTFM". Но это так не работает.
Мне нужен инструмент для быстрого и эффективного решения бизнес-задач, в котором код легко читаем, а результат его выполнения очевиден. А не ночные сессии починки продакшна, потому что 3 сеньора которые аппрувили пулл реквест, не перечитывали в очередной раз FAQ и пропустили вроде как "простую" ошибку.
Как Вы хотите, к сожалению, тоже не работает.
Я тоже так хочу, как вы говорите: чтобы было было все просто и очевидно.
Но любая сложная система (к которой относится любой развитый язык программирования) полна различных "особенностей". Вопрос только в том, насколько их много и насколько они хитрые.
Надо быть реалистами и ожидать подвоха даже от самых "простых", "удобных" и "дружелюбных" инструментов. Поэтому, чем лучше R этот самый TFM, тем меньше сюрпризов будет.
Статистические языки программированияязыки со статической типизацией точно называются статистическими?
Синтаксис Python — в чем главные подводные камни на первый взгляд легкого ЯП. Перспективы языка