Pull to refresh

Comments 146

Спасибо! А ещё большое спасибо berez за помощь с орфографией, пунктуацией и стилистикой :)

Вот, отличный пост.


sum([1, 2, 3)

Опечаточка


вам не надо думать о синхронизации доступа к переменным или о взаимных блокировках (deadlocks).

На самом деле надо. Освобождение GIL в общем случае случается в непредсказуемые моменты, и словить гонку при, например, манипуляции с одним и тем же объектом из нескольких потоков всё равно можно. Пример ниже, разумеется, говнокод, но суть проблемы всё равно демонстрирует, падая в KeyError:


from threading import Thread

d = {}

def thread1():
    while True:
        if "foo" not in d:
            d["foo"] = 0
        d["foo"] += 1

def thread2():
    while True:
        if "foo" in d:
            del d["foo"]

Thread(target=thread1).start()
Thread(target=thread2).start()

Вот несколько вариантов: использовать Docker, Windows Subsystem for Linux, Cygwin

Ну точно так же и про линукс можно сказать, что есть вариант установить питон в Wine :)


Через пару дней вы займётесь machine learning-ом

Спойлер
это верно, но я думаю имеется ввиду что из-за GIL выполняется всегда одна строка кода,
и прогнозировать лок в мультитреадед которые исполняют один и тот же код — проще,
атомарность на уровне строки
из-за GIL выполняется всегда одна строка кода

В общем случае это тоже не всегда, в i+=1 тоже может поселиться GIL, из-за чего пример ниже не хочет печатать 1000000:


from threading import Thread

i = 0

def func():
    global i
    for _ in range(100000):
        i += 1

threads = []
for _ in range(10):
    threads.append(Thread(target=func))
    threads[-1].start()

for t in threads:
    t.join()

print(i)
Спасибо за комментарий. Действительно написал двумысленно: хотел сказать, что разработчикам CPython и собственно самому CPython проще с гарантией выполнения байткода одним потоком. Подправил в тексте.
" По возможности пользуйтесь свежей версией Питона." Много пакетов, на которых этот совет не прокатывает. Например, TensorFlow.
Не то слово. Например WebRTC (Chromium) build system — gclient тоже работает только на 2.8

Причем это нигде не написано и при попытке запустить на 3.6 она просто не работает _без_ адекватной диагностики.

Впрочем на 2.8 билд тоже пожужжал часов несколько и повис.

Такой вот прекрасный язык для крупных проектов =)
А причём тут язык? Может дело в каких-то рукожопах написавших что-то криво?
Мне кажется только гугл держится за вторую версию мёртвой хваткой, у них даже в облаке до сих пор нет третьего питона в продакшене, такая вот «технологичная» компания.
Хотя в данном случае всё должно быть ок судя по pypi.org/project/tensorflow всё поддерживается до 3.6

Собственно, о третьей версии была и речь. 3.7 и далее пока не поддерживают.

Странно.
Вроде 3.7 запустили больше месяца как.
Python 3.7 is now supported officially in TensorFlow 1.13.1
Обновился давно.

Ещё программа Calibre. Ведущий разработчик заявлял, что не перейдёт на Питон 3 даже после окончания поддержки второго. То, что он индус по национальности, не имеет никакого отношения ни к этому, ни к потрясающему количеству детских ошибок при работе с ДБ.

Ну, справедливости ради, в aws тоже есть отдельные места, где до сих пор p27 только.
я хочу заметить что атрибуты начинающиеся с _ protected а с __ private.
и это не просто конвенция, у прайватс происходит name hashing, так что у вас не получитсья переопределить прайват метод.
"Проще попросить прощения, чем спрашивать разрешение" (Easier to ask for forgiveness, than permission, EAFP).

А почему? А потому что


Питон медленный

А так есть ещё


Явное лучше, чем неявное.

Но если явно делать проверку валидности значений, то это анбоксинг переменной сначала для проверки условия, а потом ещё раз анбоксинг при передаче в обработку. Ясно, что быстрее, если в самой обработке уже сишная библиотека сделает проверку и кинет исключение.


Со словарями, конечно, отдельный случай. Там действительно есть проблема проверки наличия ключа — потому что занимает столько же времени, сколько и поиск элемента по ключу. Коммон Лисп на этот случай при извлечении элемента по ключу возвращает два значения — само значение или False, если ключ не найден, и то, найден ключ или нет — потому что False может само по себе лежать в словаре по искомому ключу. В других языках принято, если ключ не найден, кидать исключение — и после Лиспа это воспринимается как несколько экзотический способ просто вернуть второе значение.


Отсюда понятно, почему рекомендуется пользоваться try-except, когда нужно достать значение, но явным if, если нужно добавить элемент при отсутствии — это ни черта не EAFP, а особенность работы хэш-таблиц. Ясен пень, если нужно вписать что-то, если его нет, — надо сначала убедиться, что его реально нет, и время на эту проверку никак не сэкономить. А вот если надо достать по ключу — то проверку действительно можно сэкономить, но нужно уметь как-то просигнализировать о неудачном поиске ключа. Можно было бы и без исключения обойтись, а возвращать, как в Лиспе, кортежем.

Со словарями, конечно, отдельный случай. Там действительно есть проблема проверки наличия ключа — потому что занимает столько же времени, сколько и поиск элемента по ключу.

т.е. очень быстро, а значит проблемы нет, тем более что есть dict.get() который позволяет задать дефолтное значение, о чём месье видимо не знает
т.е. очень быстро, а значит проблемы нет

Достаточно медленно, чтобы уже рекомендовать не делать одну и ту же операцию дважды с нарушением принципа "явное лучше неявного". Это же анбоксинг, вычисление хеша, проход по таблице, разрешение коллизий — и всё это два раза, если сначала проверять наличие ключа, а потом брать значение по нему. Ну да, макс. 2000 инструкций против макс. 1000 — это всё равно O(1), если чисто по асимптотике считать.


тем более что есть dict.get() который позволяет задать дефолтное значение

Я же об этом написал. Пожалуйста, пусть возвращает. Как отличить — это дефолтное значение, которое по странному стечению обстоятельств было по искомому ключу положено или отсутствие ключа в таблице?


Ясно, что может быть алгоритм, по которому None в словарь точно не положится, тогда его можно использовать как сигнальное значение. А что, блин, если нет?

Вот так понятнее, хотя ещё надо придумать реальные ситуации когда None нужен как значение.

Это да, но разработчик стандартной библиотеки понимает же, какой вой поднимется, сделай он "при отсутствии ключа в словаре возвращается None безо всяких там исключений".

Камрады, давайте разберём:


class NotFound:
    pass

val = dict.get(key, NotFound)
if val is NotFound:
    ...

  • без проблем.

Теперь разберём ситуацию, в которой


# допустим, что вероятность key not in dict ~ 10%
val = dict.get(key, NotFound)  

В синтетическом тесте, try..except может оказаться быстрее, например:


Много кода
N = 10000
P_IN = 0.90

d = dict(enumerate(range(int(N * P_IN))))

def sum_dict_in():
    s = 0

    for i in range(N):
        s += d.get(i, 0)
    return s

def sum_dict_try():
    s = 0

    for i in range(N):
        try:
            s += d[i]
        except KeyError:
            s += 0
    return s

if __name__ == '__main__':
    import timeit
    print(f'Testing with {N} elements and probability of element in dictionary {P_IN}')
    print(timeit.timeit("sum_dict_in()", setup="from __main__ import sum_dict_in", number=1000))
    print(timeit.timeit("sum_dict_try()", setup="from __main__ import sum_dict_try", number=1000))

У меня получилось, что try..except работает быстрее вплоть до 15% промахов.


Но вся эта проблема, как мне кажется, высосана из пальца. EAFP это же не столько о скорости выполнения и о дефолтных значениях, сколько о самом принципе: не стоит погребать логику под if valid, если not valid — это исключительная ситуация.

не стоит погребать логику под if valid, если not valid — это исключительная ситуация.

Именно, если поля в словаре межет не быть в нормальной ситуации и это лишь повод для ветвления логики, то при чём тут исключения?
Более того, надо понимать, что исключения надо генерировать когда из внешнего мира пришли кривые данные или что-то во внешнем мире пошло не так, с чем программа не может справится, а это скажем так не так много мест.
А если мы валидируем входные данные для функции предполагая что своими кривыми руками что-то не так сделали, то это уже assert'ы, которые в продашене можно выключить и убрать оверхед.

Давайте немного отойдём от словаря. Пусть это сокет, в который отправляются данные. Опять два варианта:


if socket.is_alive:
    socket.send(data)

try:
    socket.send(data)
except NetworkError:
    ...

Пример из Oracle-овского драйвера в Django:



    def close(self):
        try:
            self.cursor.close()
        except Database.InterfaceError:
            # already closed
            pass

Здесь нет проверок вроде if self.is_closed. Django не думает, что кому-то вздумается вызывать .close() до покраснения :)

Тут как раз речь про внешний мир и это правильное место для использования исключений, это не ветвление логики, а именно попытка сделать то что нужно.

Можно я немного позанудствую? dict["foo"] = NotFound :)

Если кто-то хочет навредить, то его ничем не остановить, но питон считает что всё тут взрослые люди.

Пример для демонстрации принципа не очень хороший, имхо.


Там известно, что и где может пойти не так и известно, что в этом случае делать. А если всё это известно — как раз лучше явную проверку сделать. Именно со словарями лучше делать try-except по историческим причинам, в Лиспе вот отсутствие ключа не является исключительным случаем.


Логичнее сделать пример сферического калькулятора — от пользователя считывается строка как выражение, пользователю выводится результат вычисления. Пользовательский интерфейс понятия не имеет, как проверить выражение на корректность, алгоритмическая часть понятия не имеет, что делать в случае ошибки.


И ещё там так пишете, будто вылавливание исключения делает байпасс проверок — но проверка же где-то всё равно должна выполниться. Но если проверку делает под капотом какая-то сишная библиотека — то, действительно, может оказаться быстрее, чем в самом питоне.


Ну это я просто за другой подход — что лучше один раз подумать, где может пролезть исключительная ситуация и как её не допустить, чем каждую функцию писать в предположении, что на вход поступает полная абракадабра. Задачи численного моделирования такой подход очень сильно ускоряет.

Но если проверку делает под капотом какая-то сишная библиотека — то, действительно, может оказаться быстрее, чем в самом питоне.

Да, быстрее, но при этом можно получить уязвимость в этой самой библиотеке, передав ей некорректные данные. Так что если вам важна не скорость. а безопасность, то возьмите Java проверяйте значения на входе.

Если так хочется, то лучше использовать шаблон Sentinel Object в этом случае.


SENTINEL = object()
val = dict.get(key, SENTINEL)

Тогда даже в случае, когда в dict[key] лежит объект object(), то (val is SENTINEL) == False. Ответ и на комментарий от andreymal выше.

dict.get не для проверки наличия ключа, значение по-умолчанию позволяет в некоторых случаях делать меньше проверок, например:
print(key, myDict.get(key, "N/A"), sep = ': ')
«Проще попросить прощения, чем спрашивать разрешение» (Easier to ask for forgiveness, than permission, EAFP).
А почему? А потому что
Питон медленный

Потому такой код легче читается и модифицируется: вы говорите что вам нужно получать, а клиентский код (вызываемая функция) либо возвращает результат, либо (с помощью исключения) сигнализирует об ошибке. Это задача клиентского кода проверять условия необходимые для выполнения задачи. Вызывающий код не должен знать про то что, как и где нужно проверять.

Дело в том, что лет 8-10 назад была статья, кажется про PHP, где автор предлагал избавляться от условных выражений вообще и переходить на исключения. Ветвление — дорогая операция, мол, и т.д. ЧСХ, примеры показывали, что действительно — на исключениях быстрее.


Я в те времена был совсем молодой и глупый, слышал только про Си и никак не мог понять — НО КАК? Ну, компьютер, хоть бы и на исключениях, всё равно не может магически выбирать всегда правильную ветку выполнения. Где-то там для этих исключений должны ведь производиться эти самые проверки.


Сейчас думаю, что это всё в интерпретируемых языках может действительно быть быстрее, т.к. генерация и обработка исключений написана на быстром языке, который под капотом.


А потом придумывают какую-то философию вместо того, чтобы объяснять логически, почему в таких-то случаях так-то делать действительно лучше, быстрее и чище.

Скорее всего эконмия на спичках, и в любом случае никто от условий отказываться не предлагает (кроме маргиналов всяких), у всего своё место в соответствии с семантикой.
Хорошая статья, а есть где почитать про экосистему? Откуда брать библиотеки, как совладать с пипом и почему для каких-то либ надо ставить отдельные пакеты через sudo apt install?

Потому что после npm это кажется каким-то адом. Типа все зависимости всех проектов в кучу, что? Я уже молчу (хотя хочется кричать от ужаса) что питона 2 версии и все говорят «используй третий», но сами используют второй (-_- )
Откуда брать библиотеки

pypi.org


как совладать с пипом

В каком смысле? pip install обычно просто работает. Единственное, что нужно помнить, это что pip по умолчанию пытается ставить всё глобально, и обычно нужно создать virtualenv (в npm соответственно наоборот, аналог virtualenv он создаёт автоматически в виде node_modules)


почему для каких-то либ надо ставить отдельные пакеты через sudo apt install?

Так в npm то же самое: перед запуском какого-нибудь npm install opencv нужно не забыть сделать sudo apt-get install build-essential libopencv-dev. Причина и у pip, и у npm одна и та же: чтобы собрать биндинги к нативным библиотекам.


все зависимости всех проектов в кучу

Нет, см. virtualenv


но сами используют второй

Это называется легаси :(

UFO just landed and posted this here
Экосистема сильно зависит от сферы деятельности\интересов. А так стаковерфлоу дай много ответов про Питон.

Sudo — лишь для того, что лезет в систему. Типа, библиотека Pillow использует libjpeg. А там компиляция нужна, если нет в системе.

Зависимости и кучи. Используйте virtualenv для каждого проекта — и все пакеты будут локально, в нужной песочнице. Удобно.

Да, сам сижу на 2-м. А потому что есть часть старого кода. Хотя один проект решил начать на 3-м питоне.

Но сейчас с нуля учить только 3-й питон. Ну, чтоб без вот этого u«это юникодная строка»
Почитать про экосистему можно во всяких awesome python списках, навроде такого.
С пипом проблем нет, но лучше не ставить через него ничего с sudo, а делать
pip3 install --user needed_lib
тогда всё будет ставиться в хомдиру в .local/ (только стоит проверть что .local/bin прописал в PATH)
если проектов много с разным набором либ, то чтобы не они конфликтовали стоит использовать virtualenv, это позвоялет юзать разный набор либ, но с одной версией питона, если в разных проектах нужны разные версии питона, то есть pyenv со своей приблудой для venv.
UFO just landed and posted this here

Мне кажется, нет никаких проблем установить несколько версий питона в систему без конфликтов (и даже без pyenv). Имею 2.7, 3.3, 3.4, 3.5, 3.6 и 3.7, поставленные штатным пакетым менеджером арчлинукса — отлично работают

UFO just landed and posted this here

best practice — код, не приколоченный намертво к одному-единственному конкретному окружению :) Тестирую свои проекты в окружениях от 2.7 до 3.7 (или от 3.5, если решил дропнуть второй питон), по возможности и на PyPy, от фряхи до винды и иногда даже Termux на Android — отлично работают. (Впрочем, на серьёзный продакшен я действительно не претендую). А там уже хоть pyenv, хоть docker, хоть vagga+lithos

Всё зависит от проекто/желания, конечно если проект будет деплоится в докер, то в нём его и надо тестить, а если что-то на попробовть, поэкспериментировать, то как по мне проще venv.

Если бы библиотеки Питона писались только на самом Питоне, то тогда бы подобных проблем было бы меньше. А теперь представьте, что вам нужна нативное расширение, например PIL/Pillow для обработки изображений? Есть два варианта — либо вы скачиваете кем-то скомпилированное и упаковонное расширение под вашу платформу и вашу версию Питона, либо скачиваете исходники и компилируете его сами! pip может сделать и то и другое, но для компиляции ему нужен собственно компилятор, заголовочные файлы Питона, исходники или бинарники зависимостей и т.д.


npm пришёл из экосистемы, где всё пишется на JS. А в Питоне, помимо пакетов с "чистым" Питоном есть расширения, которые могут быть написаны на C/C++ и т.д. Отсюда и разница.

UFO just landed and posted this here
все говорят «используй третий», но сами используют второй
Потому что библиотеки переписывать долго, муторно, и главное — зачем?

А питон весь в зависимостях. В папку пипа смотреть страшно.
Потому что после npm это кажется каким-то адом.

Один раз установите глобально и используйте poetry (ну или pipenv) — это `npm` для Питона.

и все говорят «используй третий», но сами используют второй

Уже давно всё не так страшно. Не помню, когда последний раз сталкивался с отсутствием поддержки тройки.
Моё знакомство с Python было в 2008. Когда мне дали задание написать генератор PDF отчётов на основе данных из PostgreSQL, я даже и не слышал об этом языке. Но он оказался, на удивление, прост и реализация задания не составила особого труда. Благо, уже на тот момент были справочные материалы и примеры кода. Идея выравнивания кода с одной стороны хороша, но, как уже упоминал автор, при большом объёме кода становится сложнее читать. И, да, каждый язык это всего лишь инструмент. Удобство использования определяется привычками и опытом.
Идея выравнивания кода с одной стороны хороша, но, как уже упоминал автор, при большом объёме кода становится сложнее читать.

атор и говорит, что это должно стимулировать не писать нечитаемый код
Не спорю. Просто при переходе с других языков, это первое непонимание, что встречается на пути. Просто нужно перестроить стиль. В принципе, других сложностей до сих пор не встречал.
атор и говорит, что это должно стимулировать не писать нечитаемый код
Бывают случаи, когда от разбиения кода на отдельные функции, он становится только ещё менее читаемым.
Ну вот в этом и работа программиста, найти лучшее решение
при большом объёме кода становится сложнее читать

Сложнее становится читать, не когда кода много, а когда пишете не в терминах предметной области (читай — не разбиваете код на понятные функции).
А что мне надо установить у пользователя моей программы?
Питон, пип и все пакеты, использованные при разработке?

Питон, пакеты и библиотеки которые требуются для работы программы. Есть также бандлеры, которые с переменным успехом собирают Питон и зависимости в единый исполняемый файл.

В Windows есть возможность упаковать всё в один EXE. В Linux качать зависимости — это норма, никто не возмутится. (Но можно и упаковать, опять же). Вот в Android придётся заставлять юзера пошаманить руками, отчего Питон под Android толком и не взлетел.

Когда я познакомился с Python, мне обещали утиную типизацию. Если оно выглядит как строка, крякаяет как строка и плавает как строка, то это и есть строка...


class X:
  def __str__(self):
    return "I'm X"

arr = [X()]
",".join(arr)

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: sequence item 0: expected str instance, X found

Oh, rly?


Потом я попробовал поймать несколько исключений:


expected = set([IOError, MemoryError, FileNotFoundError])
try:
   with open("/none"):
     pass
except expected as e:
    print("Good exception", e)

Traceback (most recent call last):
  File "<stdin>", line 4, in <module>
TypeError: catching classes that do not inherit from BaseException is not allowed

WUT?


Если написать expected = (IOError, MemoryError, FileNotFoundError), то код сработает. Причины? Утиная типизация — если оно выглядит как тапл, ведёт себя как тапл, крякает как тапл, то оно нифига не тапл. Питон ВСЁ ВИДИТ И НЕ ПРОЩАЕТ.


Но у нас же есть статическая типизация… Которая совершенно опциональна и не вызывает ошибок ни на каком этапе.


Ах, да, потом мне рассказали как пакетировать питон. С помощью wheel, setuptools distutils easy_setup через pycentral в pip. Если ничего не пропустил.


А ещё окончание итерации — это exception StopIteration. Отличный метод использовать exception'ы. Может, сделаем это стандартом?


with open("file") as f:
     raise Result(f.read())

Вот потеха будет-то...


Чуден язык, да и только.

Например наличие метода __str__ никоим образом не означает что это строка, это не апи строки, это апи приведения к строковому виду.

А что надо сделать, чтобы данный класс начал утино типизироваться к строке?

Вы путаете тёплое с мягким.
Если некий код ожидает от объекта наличие определённого метода, то он может просто дёрнуть этот метод у любого объекта, у которого такой метод есть, и коду вообще не нужно знать, от какого класса инстанцирован этот объект. Это и есть утиная типизация.

Но если код ожидает объект определённого класса, то ему нужно дать объект определённого класса. Никакой другой класс не подойдёт, какие бы вы методы в него не напихали.

Ваш пример со строкой, например, будет прекрасно работать, если вы унаследуете класс X от класса str. Но в любом случае, это уже вопрос не про утиную типизацию.

Я не путаю тёплое с мягким. Я говорю, что когда "код ожитает объект определённого класса" — это нарушение утиной типизации. Зачем он ожидает объект определённого класса? Чтобы посмотреть некоторые его атрибуты или вызывать его методы. Если я сделал те же атрибуты и дал те же методы, зачем кто-то проверяет на "определённый класс"? Как только язык начинает требовать наследоваться от str, чтобы насладиться str.join, то у нас начинается какая-то java с virtual/final и ООП как "основы всего", а не питон.

Ну то есть утиная типизация в самом языке есть, просто язык сам использует её не во всех случаях, где теоретически мог бы.
То есть ваше утверждение об отсутствии утиной типизации неверно.

amarao, давайте не будет переливать из пустого в порожнее. Как было сказано — "except принимает tuple и ничего больше, потому что у нас так принято" :) С вами с удовольствием обсудят это в Python mailing list и может даже дело дойдёт до PEP-a. Питон не закрытый язык, если у вас есть идея, как его улучшить или исправить — дело за малым :)

Окей. Я просто показал, насколько питон не такой, каким он кажется. Глубоко в недрах его стандарта много веселья и костылей уровня языка.


Исправлять их никто не будет, потому что "used in production".

Исправлять их никто не будет, потому что "used in production".

Там нечего исправлять. В первом случае дак-тайпинг вполне работает, просто вы его не поняли (ожидается Iterable и определять нужно __iter__, а не всякие __str__ и __repr__):


>>> x = 'a', 'b', 'c'
>>> ''.join(x)
'abc'

С вашим видением ситуации можно докатиться и до абсурдного


>>> ('a', 'b', 'c') == 'abc'
True
>>> object() == "<object object at 0x7fb21a5e90c0>"
True

Во втором вашем случае вполне оправданное ограничение, иначе придётся выслушивать на тупых собеседованиях тупые вопросы, вроде "что сделает этот код"


class A(BaseException):
    def __iter__(self):
        yield ValueError
        raise StopIteration

try:
    raise A
except A:
    print "A"
except ValueError:
    print "B"
except:
    print "C"

Питон и так достаточно гибкий язык для сексуальных утех, но для мазохизма не годится, да. Попробуйте джулию, что ли.

Окей, вы меня убедили. В питоне всё потрясающе! Надо больше exception'ов для нормального рабочего процесса, наподобие StopIterator, побольше внезапных isinstance. Что там там ещё хорошего питон обещает? (*args, **kwargs) как вершину динамической типизации?

Успокойся. Питон — такой же язык, как и все: просто несправдливо утверждать, что он застрял в костылях из-за какого-то там мнимого продакшена.

«мнимый продакшн»…

независимо от темы, это высказывание стоит скрижали для высечения!

P.S. https://hg.python.org/cpython/file/tip/Python/ceval.c#l5159 — причина по которой tuple логично использовать в CPython — это представления tuple в виде обычного массива с указателями. Работать с ним намного проще и быстрее чем с любой другой коллекцией.

Реализовать апи строкового класса оно описано в документации, только учитывайте, что он наследник sequence.
Но для приведённого вами примера этого не требуется, вам тут в комментарих уже отвечали.
Про исключения не очень понял что вы хотели сказать, что не так? Конечно если положить в множество ссылку на массив, а не сами значения, то оно не будет работать как множество значений.

Э… Что за ссылка на массив в моём питоне?


Возможно пример был слишком сложный. Объясняю: если вы хотите поймать несколько exception'ов в одном except, вам надо положить их в tuple. И только в tuple. Что угодно другое, каким бы итерируемым, индексируемым и иммутабельным оно не было, не прокатит. Причём ошибка, которую выдаст питон, будет феерически запутанной. Это пример №2 нарушения утиной типизации в самом языке.

Не соглашусь. Здесь затрагивается спецификация языка, в которой чёрным по белому написано:


...For an except clause with an expression, that expression is evaluated, and the clause matches the exception if the resulting object is “compatible” with the exception. An object is compatible with an exception if it is the class or a base class of the exception object or a tuple containing an item compatible with the exception.

tuple containing an item — ключевой момент. Спецификация не допускает каких либо других контейнеров в этом месте.

Так я про это и говорю.


Одной рукой пишем "утиная типизация", а другой говорим "только объекты класса tuple".


Я утверждаю, что это место в спецификации — это нарушение собственных принципов и яркий пример неконсистентности. Пример с str.join ровно о том же.


… Причём в каком-нибудь суперфашистском языке уровня Rust, где без правильных типов даже чихнуть нельзя, и то большая гибкость за счёт трейтов. А тут — динамически типизированный язык с утиной типизацией, который проверяет аргументы через isinstance. Позор, да и только.

По-поводу str.join() требующей итерируемое со строками — это тот самый случай из дзена — "Явное лучше неявного" и в какой-то мере "Принцип единой ответственности" (Single responsibility principle). От вас требуется явным образом привести объекты, пераданные str.join() к строке, так как Вы отвечаете за представление каждого объекта в строковом виде, а str.join() лишь за их конкатенацию.

Иногда для скорости приходится жертвовать чистотой кода. Класс BaseException и tuple содержит элементы, написанные на С, и их невозможно имитировать средствами Питона. Поэтому есть жёсткое требования наследования от конкретного класса, чтобы получить эти элементы. Как вам такое?


 1
 2 class Point(tuple):
 3     def __new__(self, x, y):
 4         return tuple.__new__(Point, (x, y))
 5
 6 expected = Point(IOError, FileNotFoundError)
 7 try:
 8    with open("/none"):
 9      pass
10 except expected as e:
11     print("Good exception", e)
#Работает!

Вообще, хорошая часть стандартной библиотеки Питона написана не на Питоне, что ограничивает число возможных выкрутасов с ней.
В первом примере со строкой вам нужно имитировать не класс str, а интерфейс iterable, про что уже написали.

В первом примере у меня был arr=[X()], и мне, в силу моего скудного знания питона, кажется, что list вполне интерфейс iterable реализует.

Всё-таки перенос строки с помощью слэша — не очень хорошо.
Во-первых, если после слэша вкрадётся пробел, то это всё сломает, и причина ошибки может быть довольно неочевидна.
Во-вторых, это, конечно, дело вкуса, но код со слэшами-переносами лично мне читать сложнее.

Вместо такого:
query = User.objects \
    .filter(last_visited__gte='2019-05-01') \
    .order_by('username') \
    .values('username', 'last_visited') \
    [:5]


я всегда пишу такое:

query = (User.objects
         .filter(last_visited__gte='2019-05-01')
         .order_by('username')
         .values('username', 'last_visited')
         )[:5]

+1, обычно и сам так пишу. Использовал слеши, чтобы не пугать впервые видящих подобное :)


query = (
    User.objects
    .filter(...
    ...
    .values('username', 'last_visited')
    [:5]
)```
"Ты же форматируешь код отступами?", спросил VlK. Конечно же я форматировал его. Точнее, за меня это делала спираченная Visual Studio. Она справлялась с этим чертовски хорошо. Я никогда не задумывался о форматировании и отступах — они появлялись в коде сами по себе и казались чем-то обыденным и привычным. Но крыть было нечем — код был всегда отформатирован отступами.

Вот и покрыл. В то время как космические корабли бороздят просторы вселенной, а код на условном X# можно писать практически в одну строку и он сам форматируется, в Питоне приходится всё форматировать руками, как деды форматировали.

на условном X# надо руками скобочки расставлять как деды делали

Скобочки отлично работают. Закрыл скобку-две-три ― и всё, что было внутри них, автоматом само перенеслось и само выровнялось как надо, согласно логике. Обрамил блок скобочками, он бац ― сдвинулся вправо. Убрал скобочки ― сдвинулся влево. Вырезал кусок из одной функции, вставил в другую, он на новом месте сам выровнялся как надо. А если форматирование вдруг перекосило, то наличие ошибки в коде понятно сразу, а после того как будешь исправлять форматирование руками и пасьянс не сойдётся.


Когда Питон придумывали, такого почти полного автоматизма не было. А теперь Питон придумали и уже поздно.

При копипасте могут быть неудобства, хотя редко, нормальные инструменты справляются с типовыми случаями, а форматирование перекосило это выдуманная проблема.

Под "перекосило форматирование" я имел ввиду X#: когда вставляешь из буфера или удаляешь кусок и код вокруг разъехался. Если разъехалось, значит точно есть косяк. Не туда вставил, не всё скопировал, лишнего скопировал, не всё удалил, удалил лишнее. Автоформат — это как дополнительный контроль ошибок, который выполняет IDE, а не ты руками и глазами.

Вот, кстати, можно написать редактор или плагин к ребактору, который в коде питона отображает скобочки, и позволяет их вводить, а в файл сбрасывает так любимые питоном отступы :)
за такие мысли епитимью на тебя надо наложить!
Попробуйте перейти с ed (или что у вас там?) на какую-нибудь современную иде. Они делают отступы автоматически. Вам понравится, я гарантирую!

Если речь про автоматические отступы в начале новой строки ― это фигня. Я свой код в разы больше редактирую, чем пишу с чистого листа.


Элементарное


if x < 0:
    x = 0
    n = n + 1
    if n > 100:
        w = 1

Если в конец или середину вставить несколько строк с каким-то своим отступом, современная IDE должна им отступ выправить? Конечно, чай не в блокноте пишем. А на какой? А на хз какой.


Да даже если пишем этот код с нуля и просто после w = 1 переходим на новую строку, нажать } ничем не сложнее, чем нажать Enter и Backspace для отмены одного уровня отступа.

Вобщем моё видение такое, да, в редких случаях копирования куска кода на его форматирование нужно будет чуть больше времени чем в скобочных языках, но код пишется раз, а читается много раз, читаемость важнее писяемости, на мой взгляд питон значительно более читаем.
Тут в коментах был разговор про добавление скобочек в питон, пошёл глянул примеры — ужасно, хотя на питоне я пишу меньше года, а раньше писал как все со скобками.
пример из статьи
@property
def x(self):
    return self._x

@x.setter
def x(self, val):
    if val > 10:
        self._x = val
    else:
        raise ValueError('x should be greater than 10')

...

c.x = 5

геттер/сеттер на С++
int SomeClass::getX() const {
    return this->x;
}

void SomeClass::setX(const int val) {
    if (val > 10)
        this->x = val;
    else
        std::cout << "x should be greater than 10/n";
}

...

c.setX(5);

Объём работы по написанию кода примерно одинаков.
Хотя из преимуществ вижу «подкапотную» проверку, которая выполняется для x в Питоне.
Спорное удобство лично для меня т.к. привычней когда = является = без лишних проверок. В сеттере-же я буду ожидать с 50% вероятностью проверку значения на попадание в некий интервал
Дело в том, что геттеры и сеттеры в питоне нужно писать только если нужна дополнительная логика, если нет, всё работает словно они написаны, не нужно писать шаблонного кода.
Логично, в статье указано что все члены класса открыты. Но без дополнительной логики аналогичное поведение у полей struct в C/C++ — их поля по умолчанию открыты. Тоже не нужно писать ни строчки шаблонного кода.

А если однажды понадобится переделать поле в проперти с геттером-сеттером?

С такой точки зрения да, это удобно программисту который работает над проектом сейчас. Но если на его место придёт другой, который продолжит поддержку — его явно не обрадует сеттер с условиями, скрывающийся за обычным c.x = 5. Поэтому я и пытаюсь сказать что словосочетание «правильный подход» в данном случае довольно субъективно

А по-моему это как раз правильно, это больше походит на «каноничное» ООП с типа отправкой сообщения вида «уважаемый объект, пусть x будет 5, пожалуйста?». Я любое такое присваивание воспринимаю как синтаксический сахар для setX — независимо от того, есть ли сеттер на самом деле или нет.


(Я бы сказал, что то, что в C++ так нельзя, является недостатком C++, но он всё равно не позволит себе так делать из-за возникающих накладных расходов и/или несовместимостей ABI) (Или можно, а я не в курсе?) (Гугл предлагает перегружать operator=, но это слишком странно)

Перегружать operator= как раз таки моветон, потому что выплывет именно то, о чём я говорил — неочевидность написанного кода для того кто будет его читать (возможно, для Вас самого спустя пол года работы над другой частью проекта)

Нет никаких неочевидностей. Пиша obj.field=value, программист обязан понимать, что это синтаксический сахар для отправки сообщения объекту, и произойти может что угодно — точно так же, как и при obj.setField(value). Но вы же почему-то не возмущаетесь, что при obj.setField(value) тоже может произойти что угодно? И я пишу свой код именно с такими рассуждениями, поэтому никаких проблем спустя полгода у меня нет.


Не тащите в Python свои привычки из C++. Цитату Алана Кея про C++, надеюсь, напоминать не нужно?

Здесь удобен подход Delphi, пусть закидают меня помидорами.
Мы определяем property как
  fX: integer;
..
public
.. 
  property X: integer read fX write fX;

И если нам нужно перекинуть все в сеттер, мы делаем:
  fX: integer;
  procedure SetX(AX: integer);
public
..
  property X: integer read fX write SetX;


IGR2014:
скрывающийся за обычным c.x = 5. Поэтому я и пытаюсь сказать что словосочетание «правильный подход» в данном случае довольно субъективно

Да нет, напротив. Внутренние данные объекта, его внутреннее состояние — это его личное, интимное дело. Если бы это была структура — понятно, мы просто работаем с полем. Если это объект, то у него могут быть свои взгляды на то, как реагировать на внешнее изменение. На то он и объект, на то и инкапсуляция. Никаких предположений на тему «мы закинули 5 в х, там и должно быть 5» быть не может.
Никаких предположений на тему «мы закинули 5 в х, там и должно быть 5» быть не может.

Вот такое ООП точно не нужно. Нет уж, если мы пытаемся закинуть 5 в x — пускай там будет или 5, или объяснение, почему именно 5 там быть не может.

Нет уж, если мы пытаемся закинуть 5 в x — пускай там будет или 5, или объяснение, почему именно 5 там быть не может.

Потому что это не поле данных и не простой native тип, для которых определен контракт «что положили, то и есть». Это интерфейс взаимодействия с объектом, и он сам определяет, как реагировать. Допустим, внутри него вообще нет никакого X — что тогда?
Допустим, есть максимальное допустимое значение, которое меньше 5 или более оптимальное значение Х, которое известно самому объекту (например, Х — это количество элементов, которое может хранить объект, а ему выгоднее выделять память степенями двойки).
UFO just landed and posted this here
Потому что это не поле данных и не простой native тип, для которых определен контракт «что положили, то и есть». Это интерфейс взаимодействия с объектом, и он сам определяет, как реагировать.

Да, но если этот интерфейс выглядит так же, как команда простого присваивания для "простого native типа", то уж пусть он и работает примерно так же. Если надо что-то именно сложное и странное — то использовать явный сеттер, чтоб было видно, а неявный через = пускай вообще бросает исключение всегда.


Этак можно дойти до "руль и педали — это вообще-то интерфейс взаимодействия с автомобилем, решили мы сделать ускорение / торможение поворотом руля, а повороты нажатием педалей — всё ОК, читайте документацию".


Допустим, внутри него вообще нет никакого X — что тогда?

В Питоне — оно там появится (не то чтобы мне это нравилось).


Допустим, есть… более оптимальное значение Х, которое известно самому объекту (например, Х — это количество элементов, которое может хранить объект, а ему выгоднее выделять память степенями двойки).

Тогда количество элементов должно называться Y, а сеттер для X менять именно Y на степень двойки, а X — на то, что сказали.

Да, но если этот интерфейс выглядит так же, как команда простого присваивания для «простого native типа», то уж пусть он и работает примерно так же.

Почему? Вот у вас есть класс «строка» и у него операция сложения перегружена для конкатенации. Выглядит как сложение, а действие совершенно иное. И что?
Вот у вас объект — модель человека, вы создали ее, задав массу тела — 70 кг. А с течением времени модель «потолстела». Задали 70, а сейчас 75. И?
«Похожесть на обычное присвоение» — это только ваша ментальная парадигма, нет такой обязанности. В некоторых языках, например, и сравнение, и присвоение обозначаются одним и тем же знаком — "=" и значение зависит от контекста. Нет, это конечно, конвенционный вопрос — спору нет, как решено так и будет. Но в широком смысле обязанности хранить то же значение у объекта нет. Ведь в норме объект не «выставляет» наружу никакие поля — они внутри объекта. Да, можно поле сделать публичным, если лень писать оберточный геттер-сеттер, но это частный случай.
Так что тут все последовательно: присвоение — это синтаксический сахар, а не работа с полем данных, т.к. поля данных инкапуслированы.

Этак можно дойти до «руль и педали — это вообще-то интерфейс взаимодействия с автомобилем, решили мы сделать ускорение / торможение поворотом руля, а повороты нажатием педалей — всё ОК, читайте документацию».

И это, в общем-то, будет нормальным. Еще лет 100 назад органы управления автомобилем были совершенно иными (см. например, управление Фордом-Т).

Тогда количество элементов должно называться Y, а сеттер для X менять именно Y на степень двойки, а X — на то, что сказали.

Нет, я сказал «количество элементов, которое может хранить объект», а не «количество элементов». Вы закинули в контейнер 5 элементов, там и будет 5. Но вот размер контейнера можно предрастянуть, чтобы не было многократного выделения памяти. Допустим, мы знаем, что у нас будет добавлено на этом этапе 100 элементов, мы и просим контейнер — будь готов принять не менее 100. Он говорит «ок, готов принять 128» потому что ему удобнее таким куском память выделить. Все соблюдено — 100 элементов влезет, как просили, а объем контейнера — 128.
Нет, это конечно, конвенционный вопрос — спору нет, как решено так и будет. Но в широком смысле обязанности хранить то же значение у объекта нет.

Я согласен со вторым, в широком смысле. А в узком смысле, на мой взгляд, лучше избегать неочевидного поведения, в программировании и без этого полно подводных камней. Я ж тоже не говорю, что низзя-низзя перегружать присваивание вообще никогда и ни за что. Только то, что "я бох программист, и я решаю, какая у меня семантика оператора присваивания, а если вы привыкли к другому — ваши проблемы" — это уже злоупотребление. Надо какое-то странное поведение (уровня "X должен ставиться в 42 по a.x = 3") — явный сеттер, просто чтобы синтаксически выделить.


С контейнером — как мне кажется, питоновская модель как раз нормальная. Ему говорят — сделай список на 100 элементов — он говорит, ОК, есть список, туда точно влезет 100. Просят положить туда 101 элемент — фигвам, 100 есть 100. Надо больше — просите явно. Хотя выделяет по своим внутренним соображениям. Из плюсов такого подхода — однозначная семантика выражения "сделай контейнер на N элементов и положи M объектов в конец". А сколько контейнер "теоретически готов принять" я и предлагаю спрятать в Y.

Я согласен с вашими словесными формулировками сути, но в корне не согласен с синтаксисом этих формулировок в виде ЯП. Такие моменты должны быть «прозрачны» (очевидны) в первую очередь для того кто читает/пишет код, а не для интерпретатора или компилятора.
Прежде, чем учить язык, я всегда спрашиваю себя: зачем его придумали? Какие задачи он должен был легко решать?

Для Питона ответ такой:

Его придумали как противовес Перлу. Чтобы делать то же самое, что на Перле, но с красивым и ясным синтаксисом.
Перл тут ни при чём, есть ниша для этого класса языков (быстрая разработка, небольшие проекты с возможностью роста, всякая автоматизация) и питон похоже становится лидером в этой нише несмотря на долгий кризис из-за затянувшегося перехода со второго на третий.
Как это ни при чем? Перл первым занял нишу массовых динамических языков. Чем Питон лучше Перла? Только синтаксисом.
Строгостью типизации. В Перле она слишком слабая, многие ошибки проходят незамеченными.
Ещё в Перле разрыв между стилями «для одного экрана» и «для большого проекта», то, что отлично идёт в первом, надо перерабатывать на второй (ссылки на списки вместо явных списков — самый простой пример).
Лично для меня, сложность перехода на Питон, заключается в отсутствии необходимости. Бритва Оккама. Если я уже умею решать любые задачи, более чем на шести языках программирования, на любой случай имею кое-какие готовые наработки, то трудно доказать необходимость решать задачи не так, как знаешь и умеешь.
Да никто и не заставляет, должна быть внутренняя мотивация, я вот решил что питон позволяет мне писать значительно более читаемый код и перешёл на него.
С какого языка перешли? Я вот всё не могу Перл бросить. У меня так быстро получается делать что угодно на нём, и это всё кушает мало памяти и cpu, что никак не могу понять, что с этим Питоном не так.
С перла и перешёл, понял что не воспринимаю его как эффективный инструмент.
Раз вы так любите спецсимволы и смыслоёмкие конструкции, ни в коем случае не учите J, а то без работы останетесь ;)
Плюс куча готовых библиотек на все случаи жизни
Это когда-то было так, а потом я стал наталкиваться на ситуации когда для питона есть, а для перла нет и даже юзал Inline::Python
Дзен про явное лучше неявного крайне своеобразный в Питоне. Типа self надо таскать везде, но при этом к магической комбинации подчеркиваний само собой имя класса прицепится и узнаешь ты об этом (если конечно не вызубрил все пепы) случайно из статьи на хабре.
UFO just landed and posted this here

Скобки явным образом определяют тело инструкции. Ингода, это выливается в ложное видение области — думаешь, что находишься в теле какого-нибудь огроменного if-а, а на самоме деле уже давно вылез куда-то ещё.
В Питоне, с отсутствием скобок, становится не комфортно писать подобные простыни.

Так не пишите простыни, со скобками тоже не сразу понятно в каком блоке находишся, например:
            }
          }
        }
        doSomething();
      }
    }
  }
  return x;
}

Тут помогает только подсветка скобочек в редакторе или свёртка блоков кода, что и с питонокодом работает. Так что проблема надуманная.

И ещё два варианта:


  1. Отступы не в два пробела, а 4 минимум. Я два пробела почти не воспринимаю, что они есть, что их нет.
  2. Block Structure Guides ― вертикальные полоски, которые связывают начало и конец блока (https://dailydotnettips.com/turning-onoff-structure-guide-lines-in-visual-studio-2017/)
> например:

В C/аналогах можно к каждой } ставить комментарии (типа "// for(i)"), в некоторых стилях это даже рекомендуется.
Для Питона я аналогично ставлю (если иначе слишком тяжело читается) "#end" с уточнением, что именно закрылось.

В языках с явными {} по этим самым {} можно переходить на противоположный знак (в vim по умолчанию это клавиша %). В Python такого нет, нужны другие средства навигации. Если читатель глазами не видит, то некоторые задачи сильно усложняются. Например, пусть есть конструкция if — elif — … — else на много веток. Как перейти на следующий elif?
Меня в обычной работе спасает плагин indentwise — в нём по ]= переходит, если есть следующий, иначе остаётся на месте. Но для PyCharm уже такого нет (есть collapse block, но это другое; свёртка и в vim есть, её тоже использую, но очень часто нужно видеть и что творится в таком блоке).
Самое путаное в таком — это случай, когда закрывается сразу несколько блоков — тогда это не видно в точке закрытия. Там, где это важно для чтения, я добавляю явную строку "#end" на каждый такой блок.
(Всё это, разумеется, если нет возможности ужать функции до размера, который можно окинуть одним взглядом. Но у меня сейчас обстановка, увы, именно такая.)
Привет! Это ностальгия? ))
По теме: в ходе разговора тут понял, что еще одна не очень очевидная особенность Python — чтобы нормально писать python-код, надо его отлаживать. На Си достаточно просто написать достаточно сложный код, ни разу его не запустив, только компилируя и проверяя ошибки. На python проще всего несколько раз написать ошибочный код, запустить его, посмотреть что там внутри происходит и переписать. Грубо говоря, для нормальной быстрой разработки на python нужен рабочий runtime. Из-за динамической утиной типизации.
Или надо больше доков читать и исходников библиотек.
И это тоже. Но еще и необходимость на рабочем месте запускать весь env/иметь доступ к нему. Поэтому и необходимо наличие тестовой инфраструктуры и еще и ограничитель на сложность env — большие проекты сложно на одном рабочем месте развернуть.
На Си достаточно просто написать достаточно сложный код, ни разу его не запустив

Когда я так пытался делать, у меня при первом запуске обычно случался незамедлительный сегфолт. Не потому что я тупой, а просто по невнимательности и где-то какую-то мелочь забыл прописать. Так что для «нормальной быстрой» разработки на Си нужно быть чудовищно опытным, хорошо выспавшимся и изолированным от мира, чтобы не отвлекали и не сбивали внимательность

IDE помогают )) Сильно. Особенно в крупном проекте. В Python даже в крупном проекте часто IDE не могут подсказать, что именно в этом объекте — утиная динамическая типизация.

Верю, что помогают, конечно, но блог PVS-Studio демонстрирует, что IDE и даже cppcheck помогают всё же не всегда. Слыш купи


В Python даже в крупном проекте часто IDE не могут подсказать, что именно в этом объекте

Поэтому я сейчас стал прописывать аннотации типов абсолютно везде: и PyCharm подсказывать может, и mypy позволяет выловить ошибки без запуска. Не все, конечно (утиную типизацию даже с аннотациями никто не отменял, да и легаси тоже), но тем не менее «помогают )) Сильно.»

Согласен. Собственно разница как раз в том, что в языках со статической типизацией есть принуждение ко всему этому, а в Python принуждения нет. Это помогает легче стартовать, но потом в большом проекте становится сложнее.
P.S. Пришел в голову язык с обязательными тестами.
Это же гибкость, пока тебе не нужна типизация никто не принуждает, а когда проект дозрел — вот пожалуйста.

А когда проект дозрел — уже поздно.) Взять и расставить типы в произвольном коде в общем случае довольно трудно, если он тщательно обмазан всякими там декораторами, ленивостью, генерируемыми на лету классами, getattr'ами и прочим метапрограммированием, так что mypy выпадает в осадок

Я слышал про вполне успешные кейсы, хотя проблемы есть, но в основном из-за внешних библиотек без тайпхинтов.
Выше много писали про форматирование — я просто запускаю flake8 и black (vscode поддерживает оба, кстати). То же самое можно и в CI прикрутить.
«Для товарищей на Windows всё чуточку сложнее.» — на самом деле, нет. Мы учим первокурсников data science здесь при помощи питона. Весь курс сделан на jupyter notebook. Устанавливается на всё со всем питоновским гамузом, есть обрезанный онлайн (необрезанный за деньги, насколько я понимаю), сразу не имеет проблем с графикой. Поэтому начать учиться очень просто — хоть на айфоне.
Производительность — это все сильно зависит от задачи. Мы когда-то со студентами сравнивали двумерную гидродинамику, написанную на питоне и на фортране. Фортран был лишь процентов на 20 быстрее.
А вообще язык гнусный, конечно :)
Мы когда-то со студентами сравнивали двумерную гидродинамику, написанную на питоне и на фортране. Фортран был лишь процентов на 20 быстрее.

На питоне или с numpy? Если второе — то это не "Фортран был лишь процентов на 20 быстрее", это "даже если всё, что делает питон, это передача указателей между функциями BLAS — всё равно оверхед 20%".

...
def sum(items: Iterable[Num]) -> Num:
    accum = 0
    for item in items:
        accum += item
    return accum

sum([1, 2, 3)

Ухты, прикольно, новый синтаксис! Но мой Python 3.8 почему-то не хочет работать с этим. Кричит опять :(
Sign up to leave a comment.

Articles