Pull to refresh

Comments 31

Статья хорошая, я узнал про int разделитель и…. (а ещё узнал, что хабр в комментариях игнорирует пробелы между точками, как мне тогда выделить троеточие?)

Напутствие автору перевода
Статья — это не только plain text, но и комментарии разработчика в коде, строковые переменные и иногда названия переменных. Не поленитесь и переведите их.

Названия переменных? Серьёзно?

Ну с названиями переменных я действительно перегнул, но комментарии к коду почему не перевести?
тема про Ellipsis не раскрыта мне кажется… я сам до сих пор не понимаю где и как использовать. зачем вместо pass использовать… — если это одно и тоже? pass все знают и одназначно поймут, а вот Ellipsis, с его «можно использовать вместо всего» — запутывает. так же он не замена None. если ты не передал параметр — то это None. а если хочешь Ellipsis — надо три точки писать. и вот сразу лажа — проверка на None чаще всего чтобы узнать пришли данные или нет. не то что кто то написал три точки, а именно что тебе данные вообще пришли или нет. так что не то что бы замена — просто иной функционал…

Автор забыл добавить, что почти всё это относится к питону 3.х.

Так поддержка второго завершена. Закончилась эпоха, тьфу-тьфу-тьфу
Атрибут функции имеет смысл использовать двух случаях
1. когда нужна статическая lookup-таблица или нечто подобное, что не нужно создавать при каждом вызове.
Если таких переменных состояния более одной, или они меняются от вызова к вызову, то почти наверняка лучше сделать класс — будет проще и понятнее. Если очень нужно чтобы объект был callable(), то использовать магический метод __call__().
2. при реализации декораторов, вместо замыканий, чтобы избежать соответствующих проблем
Ну и альтернативно 1.5: если код, вызывающий эту функцию, typecheck'ает ИМЕННО функцию, а не просто callable object.

В остальном… лучше не надо.
Мне кажется, самая большая «сокрытая драгоценность» — это строгая типизация. Она тоже есть в документации, про нее говорят и все такое, но блин, в подавляющем большинстве библиотек и приложений ей даже не пахнет. В том же Flask зачатки строгой типизации были добавлены всего месяц назад, в Django ей даже не пахнет. А ведь она появилась пять лет назад, в 2015 году, позволяет избежать огромной кучи ошибок и дает огромное преимущество при подсказках в IDE.
Мне кажется вы что-то напутали. То про что вы говорите — всего лишь аннотация типов, просто подсказки для программистов и IDE. А вот насчет строгой типизации — python всегда был со строгой типизацией. С динамической строгой типизацией. А если вы подумали что аннотации типов вносят в python статическую типизацию — то это не так.
Согласен, перепутал. Тем не менее задалбывает лазить по чужим исходникам в попытках понять, какой аргумент и какого типа должен быть передан в функцию.
Для этого в Python изначально была заложена «функциональность» описательных комментариев в начале функций. Если их нет в исходниках (или они не корректно написаны) — то аннотации типов не спасут — их так же могут не сделать. Если IDE не может правильно извлекать информацию о типах аргументов из этого описания — то это уже проблема IDE, а не языка.

А вообще — базовая динамическая типизация — это плохое решение (увы, свойственное многим интерпретируемым ЯП) — это, по большей части — пережитки прошлого (плохой дизайн). Лучше иметь статическую типизацию + выведение типов + составные типы + возможность в отдельных редких случаях иметь возможность объявлять динамическую переменную + иметь тип что-то на подобии Variant — для хранения и передачи уж очень динамических значений
C# — очень неплохой язык. Ему бы ещё сбросить груз наследия языка Си/С++ — вообще было бы здорово — но это уже не возможно. Но… C# — это платформа .NET — тут можно создать свой язык — покруче чем C# (правда мода на это уже прошла, из относительно популярных языков — только F# от тех же Мелко мягких назвать могу, кстати IronPython для .NET был, в своё время, как и Python for .NET)
Да возможно. Но с другой стороны, с давних времен существует PEP 257 с его docstringами, чтобы не лазить глубоко в исходный код. Но программистам же лень соблюдать такие вещи и расписывать все, ну так и аннотации типов также само не обязательны и тут может сыграть тот же самый фактор лени. Да он и играет по сути, исходя из ваших слов, что ввели с 2015 года, а все никак :)
По-моему откровенно дурацкая и почти бесполезная реализация else в цикле for (и почему только в for?). На мой взгляд — куда полезнее было — если бы в секцию «else» цикл входил в случае если не разу не выполнился (это бы как раз соответствовало оператору ветвления if — если условие выполнилось — значит переход в тело; не выполнилось — переход в else — просто для цикла переход в else уместен только на первой итерации, а у if второй итерации и быть не может — значит, аналогично, уместно только для первой). Вот такой логике else у цикла (причём любого) можно было бы найти применение (эмулировать, конечно, аналогично можно, как и показано в статье)
Текущая семантика очень удобна в контексте поиска:
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»

Тебе пришлось так развёрнуто объяснять, что делают четыре строчки кода, и ты их продолжаешь называть «читаемыми»?

ну-ну.

Я объяснил на всякий случай. Ведь речь не идёт о знакомом синтаксисе Python
На мой взгляд — строчки весьма понятны и без пояснения. Я пояснил — скорее для того, чтобы показать всю глубину заложенной смысловой логики
Причём можно сократить без снижения ясности и заменить оператор "->" командой «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 не знал. Спасибо.
Насколько помню, eval() exec() лучше не использовать в пром. коде, потому всё это «динамическое считывание» может закончится взломом и большими проблемами.
подскажите пример когда без троеточия было бы намного сложнее или вообще нельзя. Сейчас выглядит как рудимент какой-то.
UFO just landed and posted this here

Насчёт атрибутов функций, не так давно встретил их применение в стандартной библиотеке. Есть в модуле functools декоратор lru_cache, который (очевидно) добавляет к функции кэш в виде словаря. В довесок, у декорированной функции будут доступны ещё два метода: cache_info() возвращает именованный кортеж со статистикой hit/miss кэша, а cache_clear() очищает его. Плюс почти все декораторы в functools добавляют __wrapped__ с оригиналом функции.

А ... -- я когда увидел, что такое добро есть, попытался найти, где оно используется и зачем оно есть. Как я понимаю, это константа, которую добавили из-за и исключительно ради NumPy. Может круто было бы использовать её как символ подстановки при сравнении упорядоченных структур данных. Например:

assert is_like([1, 2, 3, 5, 6], [..., 3, ..., 6])

Но я такого извращения нигде не встречал)

Sign up to leave a comment.

Articles