Comments 146
Отличная статья!
Вот, отличный пост.
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 выполняется всегда одна строка кода
В общем случае это тоже не всегда, в 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)
Хороший стиль изложения
Причем это нигде не написано и при попытке запустить на 3.6 она просто не работает _без_ адекватной диагностики.
Впрочем на 2.8 билд тоже пожужжал часов несколько и повис.
Такой вот прекрасный язык для крупных проектов =)
Хотя в данном случае всё должно быть ок судя по pypi.org/project/tensorflow всё поддерживается до 3.6
Собственно, о третьей версии была и речь. 3.7 и далее пока не поддерживают.
Ещё программа Calibre. Ведущий разработчик заявлял, что не перейдёт на Питон 3 даже после окончания поддержки второго. То, что он индус по национальности, не имеет никакого отношения ни к этому, ни к потрясающему количеству детских ошибок при работе с ДБ.
и это не просто конвенция, у прайватс происходит 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
безо всяких там исключений".
Я Руби не знаю. Доки пишут, что там при создании словаря определяется, что произойдёт при отсутствии ключа.
Камрады, давайте разберём:
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 по историческим причинам, в Лиспе вот отсутствие ключа не является исключительным случаем.
Логичнее сделать пример сферического калькулятора — от пользователя считывается строка как выражение, пользователю выводится результат вычисления. Пользовательский интерфейс понятия не имеет, как проверить выражение на корректность, алгоритмическая часть понятия не имеет, что делать в случае ошибки.
И ещё там так пишете, будто вылавливание исключения делает байпасс проверок — но проверка же где-то всё равно должна выполниться. Но если проверку делает под капотом какая-то сишная библиотека — то, действительно, может оказаться быстрее, чем в самом питоне.
Ну это я просто за другой подход — что лучше один раз подумать, где может пролезть исключительная ситуация и как её не допустить, чем каждую функцию писать в предположении, что на вход поступает полная абракадабра. Задачи численного моделирования такой подход очень сильно ускоряет.
Но если проверку делает под капотом какая-то сишная библиотека — то, действительно, может оказаться быстрее, чем в самом питоне.
Да, быстрее, но при этом можно получить уязвимость в этой самой библиотеке, передав ей некорректные данные. Так что если вам важна не скорость. а безопасность, то
print(key, myDict.get(key, "N/A"), sep = ': ')
«Проще попросить прощения, чем спрашивать разрешение» (Easier to ask for forgiveness, than permission, EAFP).А почему? А потому что
Питон медленный
Потому такой код легче читается и модифицируется: вы говорите что вам нужно получать, а клиентский код (вызываемая функция) либо возвращает результат, либо (с помощью исключения) сигнализирует об ошибке. Это задача клиентского кода проверять условия необходимые для выполнения задачи. Вызывающий код не должен знать про то что, как и где нужно проверять.
Дело в том, что лет 8-10 назад была статья, кажется про PHP, где автор предлагал избавляться от условных выражений вообще и переходить на исключения. Ветвление — дорогая операция, мол, и т.д. ЧСХ, примеры показывали, что действительно — на исключениях быстрее.
Я в те времена был совсем молодой и глупый, слышал только про Си и никак не мог понять — НО КАК? Ну, компьютер, хоть бы и на исключениях, всё равно не может магически выбирать всегда правильную ветку выполнения. Где-то там для этих исключений должны ведь производиться эти самые проверки.
Сейчас думаю, что это всё в интерпретируемых языках может действительно быть быстрее, т.к. генерация и обработка исключений написана на быстром языке, который под капотом.
А потом придумывают какую-то философию вместо того, чтобы объяснять логически, почему в таких-то случаях так-то делать действительно лучше, быстрее и чище.
Потому что после 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
но сами используют второй
Это называется легаси :(
Sudo — лишь для того, что лезет в систему. Типа, библиотека Pillow использует libjpeg. А там компиляция нужна, если нет в системе.
Зависимости и кучи. Используйте virtualenv для каждого проекта — и все пакеты будут локально, в нужной песочнице. Удобно.
Да, сам сижу на 2-м. А потому что есть часть старого кода. Хотя один проект решил начать на 3-м питоне.
Но сейчас с нуля учить только 3-й питон. Ну, чтоб без вот этого u«это юникодная строка»
С пипом проблем нет, но лучше не ставить через него ничего с sudo, а делать
pip3 install --user needed_lib
тогда всё будет ставиться в хомдиру в .local/ (только стоит проверть что .local/bin прописал в PATH)
если проектов много с разным набором либ, то чтобы не они конфликтовали стоит использовать virtualenv, это позвоялет юзать разный набор либ, но с одной версией питона, если в разных проектах нужны разные версии питона, то есть pyenv со своей приблудой для venv.
Мне кажется, нет никаких проблем установить несколько версий питона в систему без конфликтов (и даже без pyenv). Имею 2.7, 3.3, 3.4, 3.5, 3.6 и 3.7, поставленные штатным пакетым менеджером арчлинукса — отлично работают
best practice — код, не приколоченный намертво к одному-единственному конкретному окружению :) Тестирую свои проекты в окружениях от 2.7 до 3.7 (или от 3.5, если решил дропнуть второй питон), по возможности и на PyPy, от фряхи до винды и иногда даже Termux на Android — отлично работают. (Впрочем, на серьёзный продакшен я действительно не претендую). А там уже хоть pyenv, хоть docker, хоть vagga+lithos
Если бы библиотеки Питона писались только на самом Питоне, то тогда бы подобных проблем было бы меньше. А теперь представьте, что вам нужна нативное расширение, например PIL/Pillow для обработки изображений? Есть два варианта — либо вы скачиваете кем-то скомпилированное и упаковонное расширение под вашу платформу и вашу версию Питона, либо скачиваете исходники и компилируете его сами! pip
может сделать и то и другое, но для компиляции ему нужен собственно компилятор, заголовочные файлы Питона, исходники или бинарники зависимостей и т.д.
npm пришёл из экосистемы, где всё пишется на JS. А в Питоне, помимо пакетов с "чистым" Питоном есть расширения, которые могут быть написаны на C/C++ и т.д. Отсюда и разница.
все говорят «используй третий», но сами используют второйПотому что библиотеки переписывать долго, муторно, и главное — зачем?
А питон весь в зависимостях. В папку пипа смотреть страшно.
Потому что после npm это кажется каким-то адом.
Один раз установите глобально и используйте poetry (ну или pipenv) — это `npm` для Питона.
и все говорят «используй третий», но сами используют второй
Уже давно всё не так страшно. Не помню, когда последний раз сталкивался с отсутствием поддержки тройки.
Идея выравнивания кода с одной стороны хороша, но, как уже упоминал автор, при большом объёме кода становится сложнее читать.
атор и говорит, что это должно стимулировать не писать нечитаемый код
атор и говорит, что это должно стимулировать не писать нечитаемый кодБывают случаи, когда от разбиения кода на отдельные функции, он становится только ещё менее читаемым.
при большом объёме кода становится сложнее читать
Сложнее становится читать, не когда кода много, а когда пишете не в терминах предметной области (читай — не разбиваете код на понятные функции).
Питон, пип и все пакеты, использованные при разработке?
Питон, пакеты и библиотеки которые требуются для работы программы. Есть также бандлеры, которые с переменным успехом собирают Питон и зависимости в единый исполняемый файл.
В 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())
Вот потеха будет-то...
Чуден язык, да и только.
А что надо сделать, чтобы данный класс начал утино типизироваться к строке?
Если некий код ожидает от объекта наличие определённого метода, то он может просто дёрнуть этот метод у любого объекта, у которого такой метод есть, и коду вообще не нужно знать, от какого класса инстанцирован этот объект. Это и есть утиная типизация.
Но если код ожидает объект определённого класса, то ему нужно дать объект определённого класса. Никакой другой класс не подойдёт, какие бы вы методы в него не напихали.
Ваш пример со строкой, например, будет прекрасно работать, если вы унаследуете класс 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"
Питон и так достаточно гибкий язык для сексуальных утех, но для мазохизма не годится, да. Попробуйте джулию, что ли.
P.S. https://hg.python.org/cpython/file/tip/Python/ceval.c#l5159 — причина по которой tuple логично использовать в CPython — это представления tuple в виде обычного массива с указателями. Работать с ним намного проще и быстрее чем с любой другой коллекцией.
Но для приведённого вами примера этого не требуется, вам тут в комментарих уже отвечали.
Э… Что за ссылка на массив в моём питоне?
Возможно пример был слишком сложный. Объясняю: если вы хотите поймать несколько 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, про что уже написали.
Во-первых, если после слэша вкрадётся пробел, то это всё сломает, и причина ошибки может быть довольно неочевидна.
Во-вторых, это, конечно, дело вкуса, но код со слэшами-переносами лично мне читать сложнее.
Вместо такого:
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]
"Ты же форматируешь код отступами?", спросил VlK. Конечно же я форматировал его. Точнее, за меня это делала спираченная Visual Studio. Она справлялась с этим чертовски хорошо. Я никогда не задумывался о форматировании и отступах — они появлялись в коде сами по себе и казались чем-то обыденным и привычным. Но крыть было нечем — код был всегда отформатирован отступами.
Вот и покрыл. В то время как космические корабли бороздят просторы вселенной, а код на условном X# можно писать практически в одну строку и он сам форматируется, в Питоне приходится всё форматировать руками, как деды форматировали.
Скобочки отлично работают. Закрыл скобку-две-три ― и всё, что было внутри них, автоматом само перенеслось и само выровнялось как надо, согласно логике. Обрамил блок скобочками, он бац ― сдвинулся вправо. Убрал скобочки ― сдвинулся влево. Вырезал кусок из одной функции, вставил в другую, он на новом месте сам выровнялся как надо. А если форматирование вдруг перекосило, то наличие ошибки в коде понятно сразу, а после того как будешь исправлять форматирование руками и пасьянс не сойдётся.
Когда Питон придумывали, такого почти полного автоматизма не было. А теперь Питон придумали и уже поздно.
Под "перекосило форматирование" я имел ввиду X#: когда вставляешь из буфера или удаляешь кусок и код вокруг разъехался. Если разъехалось, значит точно есть косяк. Не туда вставил, не всё скопировал, лишнего скопировал, не всё удалил, удалил лишнее. Автоформат — это как дополнительный контроль ошибок, который выполняет IDE, а не ты руками и глазами.
Если речь про автоматические отступы в начале новой строки ― это фигня. Я свой код в разы больше редактирую, чем пишу с чистого листа.
Элементарное
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% вероятностью проверку значения на попадание в некий интервал
А если однажды понадобится переделать поле в проперти с геттером-сеттером?
А по-моему это как раз правильно, это больше походит на «каноничное» ООП с типа отправкой сообщения вида «уважаемый объект, пусть x будет 5, пожалуйста?». Я любое такое присваивание воспринимаю как синтаксический сахар для setX — независимо от того, есть ли сеттер на самом деле или нет.
(Я бы сказал, что то, что в C++ так нельзя, является недостатком C++, но он всё равно не позволит себе так делать из-за возникающих накладных расходов и/или несовместимостей ABI) (Или можно, а я не в курсе?) (Гугл предлагает перегружать operator=, но это слишком странно)
Нет никаких неочевидностей. Пиша obj.field=value, программист обязан понимать, что это синтаксический сахар для отправки сообщения объекту, и произойти может что угодно — точно так же, как и при obj.setField(value). Но вы же почему-то не возмущаетесь, что при obj.setField(value) тоже может произойти что угодно? И я пишу свой код именно с такими рассуждениями, поэтому никаких проблем спустя полгода у меня нет.
Не тащите в Python свои привычки из C++. Цитату Алана Кея про C++, надеюсь, напоминать не нужно?
Мы определяем 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 или более оптимальное значение Х, которое известно самому объекту (например, Х — это количество элементов, которое может хранить объект, а ему выгоднее выделять память степенями двойки).
Потому что это не поле данных и не простой 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.
Для Питона ответ такой:
Его придумали как противовес Перлу. Чтобы делать то же самое, что на Перле, но с красивым и ясным синтаксисом.
Скобки явным образом определяют тело инструкции. Ингода, это выливается в ложное видение области — думаешь, что находишься в теле какого-нибудь огроменного if-а, а на самоме деле уже давно вылез куда-то ещё.
В Питоне, с отсутствием скобок, становится не комфортно писать подобные простыни.
}
}
}
doSomething();
}
}
}
return x;
}
Тут помогает только подсветка скобочек в редакторе или свёртка блоков кода, что и с питонокодом работает. Так что проблема надуманная.
И ещё два варианта:
- Отступы не в два пробела, а 4 минимум. Я два пробела почти не воспринимаю, что они есть, что их нет.
- Block Structure Guides ― вертикальные полоски, которые связывают начало и конец блока (https://dailydotnettips.com/turning-onoff-structure-guide-lines-in-visual-studio-2017/)
В C/аналогах можно к каждой } ставить комментарии (типа "// for(i)"), в некоторых стилях это даже рекомендуется.
Для Питона я аналогично ставлю (если иначе слишком тяжело читается) "#end" с уточнением, что именно закрылось.
Меня в обычной работе спасает плагин indentwise — в нём по ]= переходит, если есть следующий, иначе остаётся на месте. Но для PyCharm уже такого нет (есть collapse block, но это другое; свёртка и в vim есть, её тоже использую, но очень часто нужно видеть и что творится в таком блоке).
Самое путаное в таком — это случай, когда закрывается сразу несколько блоков — тогда это не видно в точке закрытия. Там, где это важно для чтения, я добавляю явную строку "#end" на каждый такой блок.
(Всё это, разумеется, если нет возможности ужать функции до размера, который можно окинуть одним взглядом. Но у меня сейчас обстановка, увы, именно такая.)
По теме: в ходе разговора тут понял, что еще одна не очень очевидная особенность Python — чтобы нормально писать python-код, надо его отлаживать. На Си достаточно просто написать достаточно сложный код, ни разу его не запустив, только компилируя и проверяя ошибки. На python проще всего несколько раз написать ошибочный код, запустить его, посмотреть что там внутри происходит и переписать. Грубо говоря, для нормальной быстрой разработки на python нужен рабочий runtime. Из-за динамической утиной типизации.
Или надо больше доков читать и исходников библиотек.
TDD :)
На Си достаточно просто написать достаточно сложный код, ни разу его не запустив
Когда я так пытался делать, у меня при первом запуске обычно случался незамедлительный сегфолт. Не потому что я тупой, а просто по невнимательности и где-то какую-то мелочь забыл прописать. Так что для «нормальной быстрой» разработки на Си нужно быть чудовищно опытным, хорошо выспавшимся и изолированным от мира, чтобы не отвлекали и не сбивали внимательность
Верю, что помогают, конечно, но блог PVS-Studio демонстрирует, что IDE и даже cppcheck помогают всё же не всегда. Слыш купи
В Python даже в крупном проекте часто IDE не могут подсказать, что именно в этом объекте
Поэтому я сейчас стал прописывать аннотации типов абсолютно везде: и PyCharm подсказывать может, и mypy позволяет выловить ошибки без запуска. Не все, конечно (утиную типизацию даже с аннотациями никто не отменял, да и легаси тоже), но тем не менее «помогают )) Сильно.»
P.S. Пришел в голову язык с обязательными тестами.
А когда проект дозрел — уже поздно.) Взять и расставить типы в произвольном коде в общем случае довольно трудно, если он тщательно обмазан всякими там декораторами, ленивостью, генерируемыми на лету классами, getattr'ами и прочим метапрограммированием, так что mypy выпадает в осадок
Производительность — это все сильно зависит от задачи. Мы когда-то со студентами сравнивали двумерную гидродинамику, написанную на питоне и на фортране. Фортран был лишь процентов на 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 почему-то не хочет работать с этим. Кричит опять :(
Знакомство с Python для камрадов, переросших «язык A vs. язык B» и другие предрассудки