Как стать автором
Обновить

Комментарии 37

Интересная и полновесная статья.

Давно знаком с логическими операторами, но вот про truthy и falsy не слыхал)

Плюсик в карму автору🙃

Термины для тех кто не знаком с приведением типов в языках. Слышал такое использование только в контексте JS, где приведение типов местами крайне неочевидное и какой-нибудь [] + {} имеет определённый результат, а не ошибку исполнения.

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

Пока вы не вызываете явно bool({}) в тех же условиях, складываете 1+1.0 и делите 3.1415/2 оно таки будет иметь неявное приведение типов, просто не такое обширное и багопродуцирующее, как в JS. Си тоже имеет некоторое количество неявных приведений типов, в С++ приведение типов расширяемо, а например в Rust приведение надо делать самостоятельно - нельзя написать просто if my_int { ... } , нужно явно привести его к булеву типу: if my_int != 0 { ... }. Все три языка также имеют строгую типизацию, хоть и отличную по строгости от питона и друг от друга.

Да, "смешанная арифметика" в Питоне выбивается из картины, там числа неявно расширяются до нужного типа. bool({}) - это же явное преобразование, причём тут оно? Вот, наприемер, True + False работает (потому что логический тип это подкласс целый чисел), а True + {} не сработает. Если считать, что любой объект неявно преобразуется к логическому типу, то непонятно почему поледний пример не работает.

bool({}) - это же явное преобразование, причём тут оно?

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

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

Вообще, C++ считается языком с нестрогой (слабой) типизацией.

По сути, кличками truthy и falsy обозначаются ненулевые и нулевые объекты в стеке. Нулевое число, пустая ссылка (на пустую коллекцию или последовательность) - это всё "чиста нули" в машинной реализации. А ссылка на объект или непустую коллекцию/последовательность - уже число (адрес больше нуля). То есть, машинное время на подобное "приведение типов" не тратится совсем.

С какого перепугу ссылка на пустой массив - это "чиста нули"? Пустой массив всё ещё объект со своим адресом в памяти, и есть сценарии когда важно, расположены ли два пустых массива по одному адресу.

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

В языке Питон нет встроенного типа данных "массив".]

А это что - []? Массив, список - какая в данном случае разница?

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

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

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

Что там не просто? Непросто было написать такую длинную статью по информации, которую можно описать 3-4 предложениями. А так, ничего сложного нет. Приведение типов и приоритет операций есть во многих языках

А зачем?

Возьмем, например, truthy и falsy — как часто вам приходилось моделировать тип, который ведет себя как Boolean? В каком контексте это может использоваться? Зачем? Чтобы сэкономить время (серьезно?)?

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

Я вам гарантирую, что в пайтоне (точнее, людьми, которые прогают на пайтоне, коих я знаю много или вижу их код) приведение к Boolean используется очень часто. Самый простой вариант, описанный в статье - это проверка пустой ли список (массив). По такой же логике делают и проверку объектов пустой он или нет, ну, например, объект, который собирает логи - if-ом проверяем пустой или нет (к примеру не придираться, быстро сгенерировал в голове). Другая сторона этой фичи: реализация ||= (не знаю есть ли такое в шарпе, короче, аналог += только вместо + операция "или"). Выглядит так: input_dict = input_dict or {} # определяем дикт если передали None. Это все типично для пайтона

Есть такое в шарпе, есть. Только вместо логического "или" используется специальный оператор для проверки на null/None: input_dict ??= new()

Проверка объектов на наличие данных в нём с помощью оператора if - это широко распространённая идиома в Python мире, очень условный пример:

data = get_data()
if data:
    do_something(data)
else:
   raise Exception("No data")

Если get_data возвращает не список или другой стандартный контейнер, а экземпляр пользовательского класса-контейнера, то в нём достаточно реализовать некоторые магические методы (__len__, например), чтобы приведённый выше код работал точно так же.

Ещё пример: предположим есть класс, экземпляры которого могут быть "деактивированы" в процессе работы программы и не должны участвовать в бизнес логике. Если в классе реализовать метод __bool__ то можно будет такие экземпляры отфильтровавать с помощью оператора if (тоже очень искусственный пример):

for handler in (h for h in handlers if h):
    handler()

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

Мне кажется, что шарписта (я надеюсь так корректно называть) смутили эти "трули"). На самом деле практически никто не оперирует этими понятиями. Все знают, что у базовых типов своя реакция на проверку boolean-ом, в основном очевидная. Все остальное определяется методом bool, а вот как он реализован, логично в контексте класса или нет, то уже вопрос к программисту

Вопрос из разряда "Если есть СИ Шарп, то зачем вы используете питон?" :)

Я тоже довольно долго писал на С# в параллели с питоном. И питоновского if not obj или if obj is not None мне в шарпе очень недоставало - банально это выглядит интуитивнее чем шарповый набор скобок, знаков равенства и восклицательного знака в аналогичных конструкциях. Года четыре уже не пишу на шарпе, но если не ошибаюсь, там сейчас можно написать if (obj is null), что с точки зрения правильнее чем if (obj == null). С другой стороны, в питоне мне очень недостаёт условной точечной нотации some?.do() - когда обращение к методу даже не произойдёт, если some - это null. И эти примеры можно продолжать долго.

Ну и отвечая на вопрос

А зачем?

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

как часто вам приходилось моделировать тип, который ведет себя как Boolean?

Почти никогда. Но это и не нужно. Потому что в питоне любой тип ведёт себя как булев.

В каком контексте это может использоваться?

В контексте проверки объекта на None, например. Или на условие наличия хотя бы одного элемента в коллекции. Примеры есть в статье.

Зачем? Чтобы сэкономить время (серьезно?)?

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

там сейчас можно написать if (obj is null)

А ещё там сейчас можно написать

if (obj is {})

что будет означать "не нулл". :)

Ага, оно там настолько запрещено, что есть отдельные operator bool, operator true и operator false

Вы прочитали мои мысли)

В философии питона есть:

Явное лучше неявного

И вот все это противоречит данному утверждению.

print(a < c is True)

Уже представляю как это добавляется в список вопросов для собесов на джуна :D

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

Да, давайте все приколы из эпического сборника `WTF, Python?!" добавим в вопросы на собесе, чтобы уж точно собес никто никогда не прошёл ))

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

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

Возможно статья должна была называться "как не надо писать на питоне", но надо же набивать лайки.
Поэтому вместо "преобразование типов" и "явное лучше неявного" пришлось писать "секреты" на x страниц.

Именно секреты. В телеге автора, перед выпуском данной статьи был небольшой тест по данной теме, результаты очень удручающие. Хотя немного ранее данная тема им затрагивалась, так на несколько строк (что не очень то сильно помогло пройти данные тесты)

Такой код не должен проходить code-rewiew.

Проверка коллекции на пустоту должна проводиться интуитивно понятным способом, а-ля isEmpty()

https://peps.python.org/pep-0008/#programming-recommendations

Если код соответствующий требованиям официального стандарта на оформление не должен проходить ревью, то какой тогда должен?

К truthy объектам так же относится объект Ellipsis.
Экземпляры пользовательских классов так же по-умолчанию truthy, но это поведение можно переопределить с помощью магических методов __bool__, __len__, может ещё и других - лень вспоминать.

Автор молодец, но всё же ему ещё есть что поизучать. Судя по разбору примера

print(not 1 == 2 or 3 == 3 and 5 == 6)

с концепцией неполного или быстрого вычисления условий (принятой в большинстве актуальных языков программирования, включая Python) он незнаком. В этом примере вообще не будут вычисляться условия 3 == 3 и 5 == 6 , так как уже при вычислении not 1 == 2 становится ясным результат всего выражения. Легко проверить на практике таким примером:

print((not 1 == 2) or ((2/0) == 0) and ((1/0) == 0))

Будет просто True без всяких ZeroDivisionError: division by zero

if value == True:

Разве это имеет отношение к truthy и falsy? На первый взгляд тут просто вычисление значения логического выражения, и использование тождества "x == True === x"?

В примеры надо бы добавить, что bool('0') is True, и то же для bool('0.0')... Есть виды опыта, после которого это не совсем очевидно.

print(not 1 == 2 or 3 == 3 and 5 == 6)

Согласно приоритету операторов в первую очередь вычисляются выражения 1 == 23 == 3 и 5 == 6, в результате чего исходное выражение принимает вид not False or True and False. Далее выполняется оператор not, возвращая значение True, после него — оператор and, возвращая значение False. Выражение принимает вид True or False. Последним выполняется оператор or, возвращая общий результат выражения — значение True.

Разве будут вычисляться значения 3 == 3 and 5 == 6 ? У нас ведь сразу после вычисления not 1 == 2 вернулся True , а значит оператор or может завершать работу.

Я проверил это в Python v.3.12.2, подставляя функцию t() вместо каждого числа, и получил, что функция выводит строку только если подставить её вместо двух первых чисел (1 или 2)

def t():
    print("kekw")
    return 10

print(not 1 == 2 or 3 == 3 and 5 == 6) 

Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации

Истории