Comments 31
Напутствие автору перевода
Статья — это не только plain text, но и комментарии разработчика в коде, строковые переменные и иногда названия переменных. Не поленитесь и переведите их.
Автор забыл добавить, что почти всё это относится к питону 3.х.
1. когда нужна статическая lookup-таблица или нечто подобное, что не нужно создавать при каждом вызове.
Если таких переменных состояния более одной, или они меняются от вызова к вызову, то почти наверняка лучше сделать класс — будет проще и понятнее. Если очень нужно чтобы объект был callable(), то использовать магический метод __call__().
2. при реализации декораторов, вместо замыканий, чтобы избежать соответствующих проблем
Ну и альтернативно 1.5: если код, вызывающий эту функцию, typecheck'ает ИМЕННО функцию, а не просто callable object.
В остальном… лучше не надо.
А вообще — базовая динамическая типизация — это плохое решение (увы, свойственное многим интерпретируемым ЯП) — это, по большей части — пережитки прошлого (плохой дизайн). Лучше иметь статическую типизацию + выведение типов + составные типы + возможность в отдельных редких случаях иметь возможность объявлять динамическую переменную + иметь тип что-то на подобии Variant — для хранения и передачи уж очень динамических значений
for el in array:
if matches(el):
do_job_with(el)
break
else:
no_matched_found()
Кстати, у while тоже есть else.
for el in array.filter(matches):
do_job_with(el)
else:
no_matched_found()
или даже такой (для данного примера)
array.filter(matches).do(do_job_with).else(no_matched_found)
Простите за мой квази-Python который уже и не Python вовсе
Извините, но ваш вариант читается хуже, имхо.
Код нужно не только писать, но и читать. Иногда chaining выглядит красивее, иногда делает кашу там где не нужно.
Вот в этом примере, чейнинг делает всё только хуже и медленнее. Особенно учитывая, что matches может быть составным условием на две строчки, а do_job_with и no_matches_found могут быть по три-десять строчек каждый. Выписывать в lambda или в отдельностоящие функции в питоне будет… disgusting.
Но, безусловно, в каком нибудь другом языке это будет выглядеть привычнее.
Хотя вместо do().else() я пять раз из шести предпочёл бы сохранить результат array.filter() в переменную и проверить обычным if.
matched = array.filter{|el| matches}
if !matched.empty?
matched.each{|v| do_job_with(v)}
else
no_matched_found()
end
Да, длиннее. Но понятнее.
Мне, вот, Ваш пример кажется куда более корявым и трудночитаемым. Всё Ваше объяснение про многосторонность — не более чем полемика почему «оранжевый код краше синего», хотя да — тут «ноги растут» из определённых традиций стилистики программирования на языке Python. Я лишь пытался порассуждать над красотой к которой, по моему-мнению, надо бы стремиться. Но с Python тут уже ничего не поделаешь — в нём много заложено такого, что, например мне не нравится, и это уже его коренные черты (но всегда найдутся те — кто яро будет защищать их самобытность и красоту).
А насчёт мгногострочного кода — тут да — ничего не поделаешь — только упаковывать в анонимные лямда-функции раз Python такой специфический в этом плане — что мне больше всего в нём не нравится — прям категорически! Но в указанном примере всё уже было упаковано — поэтому, именно для него я и привёл свой вариант.
Вот так могло бы быть ещё красивее (а может и нет, многое зависит от привычки, но не всегда и после привыкания получается удобство) — будь такая поддержка (совсем не Python):
(el <- array) ->
filter(@matches(@el)) ->
do_job_with(@el)
undone -> no_matched_found
(не знаю как тут сохранить правильную табуляцию — но она не принципиальна — не Python же, к счастью)
Построчное пояснение:
1. Открываем чтение поток поэлементного чтения из источника «array», позиция чтения в «el»
2. Вызываем команду «filter» у открытого потока чтения (если поддерживается), передаём ей ссылку на некий предикат «matches» (через оператор получения адреса "@"), в данном случае, вероятно функцию (хотя кто его знает, что это на самом деле — может декларативное описание выражение фильтра — что предпочтительнее — так как его можно оптимизированно использовать, ускорив фильтрацию, например за счёт грамотного применения имеющихся индексов у источника потока). С предикатом связывается переменная элементов потока «el» — так же через получение адреса (что гарантирует, что будет передача будет по ссылке, а не сразу будет закреплено за входящим аргументом текущее значение переменной). После чего получаем модифицированный поток.
3. Передаём элементы потока в предикат обработки «do_job_with» (опять-таки, мы не знаем, что это, но пусть для простоты будет функция). Передача «el» так же по ссылке, а не текущее значение перед началом обработки.
4. Вызываем команду «undone» (всегда доступна у потоков) — если вызов ни одной из предыдущих команд (в данном случае «filter»; а их, может быть несколько) не вернули результат. Она передаст поток в дальнейшую обработку и вызовет предикат «no_matched_found»
Итого имеем очень высокую абстракцию кода, при сохранении достаточно выского уровня читаемости и понимания заложенной логики, и сокрытия реальных процессов обработки данных (но так и задумано).
А многостронные уструкции просто размещаем в классических фигруных скобках "{ }" (как уже сказал — это совсем не Python и даже не C++/C# — это уже скорее пример некоего абстрактного программирования на квазиязыке 5-го поколения)
«undone» намеренно ввёл вместо «else» или "?"/"??" что бы не смешивать их синтаксические конструкции с командами обработки потоков. Команды — не являются зарезервированными словами. А внутри их блоков могут применятся указанные зарезервированные слова для организации какого-то ветвления и проверки условий.
Возможно «undone» лучше было бы назвать «defaut» — усиливая аналогию с паттерн метчингом — ведь тут команды именно так обрабатываются — перебираются пока их паттерн не сработает. Но мне тут просто не очень нравится само слово «default» — оно больше ассоциируется у меня со значениями по умолчанию, чем с ветвлением (хотя и используется «испокон веков» в «switch» конструкциях). Возможно были бы более «красивыми» такие слова как "_", «another», «any», «nothing» или даже просто ввести отдельную команду «empty» — это всё к семантики данного выражении не имеет отношения — это всё элементы архитектуры конкретной реализации интерфейса потока «array»
Тебе пришлось так развёрнуто объяснять, что делают четыре строчки кода, и ты их продолжаешь называть «читаемыми»?
ну-ну.
На мой взгляд — строчки весьма понятны и без пояснения. Я пояснил — скорее для того, чтобы показать всю глубину заложенной смысловой логики
Причём можно сократить без снижения ясности и заменить оператор "->" командой «to», а команду «undone» на команду «empty»
array to
filter(matches) to do_job_with
epmty to no_matched_found
Здесь элементы из источника «array» автоматически направляются в первый аргумент функций (когда он есть) — это техника подсмотрена в языке Kotlin.
А обращение к имени функции без круглых скобок автоматически приравнивается к получению адреса (когда не нужно явно указывать связь со значениями аргументов — а в данном случае единственный аргумент передаётся автоматически).
В таком виде читаемость почти идеальна — даже если не вникать в тонкости семантики языка
Первый пример — это катастрофа, а не программирование. Сохранение в атрибуты функции ничем не лучше сохранения в рандомное другое место в globals(). Глобальная мутабельная переменная. В Rust такое разрешают только в unsafe{}
блоке. А автору — всё божья роса. Ух ты, я могу манглить что попало!
Материал, конечно интересный. Особенно для новичков в Python. Но...
Во-первых, не указано что это перевод. И перевод сделанный куриной лапой на коленке! И текст не оформлен нормально.
Во-вторых, Уже давно существует грамотный перевод этого текста https://proglib.io/p/skrytye-sokrovishcha-python-2021-04-23
>>>
>>> def f1(a):
def f2(b):
f1.state+=1
print(a+b)
f1.state=0
return f2
>>> x=f1(10)
>>> x(20)
30
>>>
По теме статьи, про int не знал. Спасибо.
Насчёт атрибутов функций, не так давно встретил их применение в стандартной библиотеке. Есть в модуле functools декоратор lru_cache, который (очевидно) добавляет к функции кэш в виде словаря. В довесок, у декорированной функции будут доступны ещё два метода: cache_info() возвращает именованный кортеж со статистикой hit/miss кэша, а cache_clear() очищает его. Плюс почти все декораторы в functools добавляют __wrapped__
с оригиналом функции.
А ...
-- я когда увидел, что такое добро есть, попытался найти, где оно используется и зачем оно есть. Как я понимаю, это константа, которую добавили из-за и исключительно ради NumPy. Может круто было бы использовать её как символ подстановки при сравнении упорядоченных структур данных. Например:
assert is_like([1, 2, 3, 5, 6], [..., 3, ..., 6])
Но я такого извращения нигде не встречал)
Сокрытые драгоценности Python