Из серии "за что мне нравится Python"... Сегодня сформулировал у себя в голове еще одну приятную штуку.
Часто бывает нужно, чтобы у переменной какого-то типа было "никакое" или "неопределенное" значение. Скажем, еще непосчитанное количество, заданное int'ом или время создания объекта, который еще до конца не создан, заданное timestamp'ом.
В языке со статическим типизированием у элементарных типов (целое, дата/время) таких специальных значений нет, и чтобы это обойти есть, в общем-то, всего два подхода:
-
Если вы используете не все значения типа, то можно зарезервировать какое-то из значений для показывания неопределенности. Например значение "-1" для количества, которое должно быть неотрицательным или "31.12.1899" для даты.
Минусов у этого решения я усматриваю два. Во-первых, ухудшается читаемость кода: везде, где вы встречаете "
if(x!=-1)
" мозгу приходится разрешать лишнюю абстракцию, вспоминая, что "-1" в данном случае означает неопределенность". Хотя это можно обойти заданием константы с понятным именем.Второй минус более синий. Поскольку компилятор не в курсе, что это значение особенное, он будет продолжать с ним работать без всяких ошибок. Скажем, сложит известное количество с неизвестным. * Второй способ, более безопасный — сделать отдельный маленький класс, который будет хранить значение нужного типа, а также признак инициализированности. И при всех попытках прочитать значение сначала проверять этот признак и если надо, выкидывать exception.
Недостаток этого способа заключается в простом неудобстве. Придется наверняка делать у этого класса некий метод доступа "value()", в котором будут идти проверки, и вместо "
x=y+z
" писать что-то вроде "x.value=y.value+z.value
". Кроме того, в зависимости от языка, еще придется заботиться об управлении памятью, о том, что классы передаются по умолчанию по ссылке, а не по значению.Ну и кроме того, сам класс, не несущий никакой творческой нагрузки, просто загромождает код.
А что же нам предлагает Python. Поскольку у переменной нет привязки к типу, то любой переменной в любой момент можно назначить значение None
(аналог NULL'а, Nil'а). А потом, когда надо, присвоить ей нужное реальное значение. В итоге:
- Значение
None
четко означает отсутствие значения. -
Проверки красиво читаются:
if not create_date: create_date=datetime.today()
-
Язык не даст смешать это значение с другими: сложить с десятью, взять пятую букву, проверить на "раньше сегодня"...
Вот так все логично и лаконично.
Комментарии: 14
Не буду разводить здесь Вашу нелюбимую дискуссию, но мне за тот же null (и не только) нравится PHP. :-)
Оне не моя нелюбимая :-). Я просто не знаю PHP настолько близко, чтобы о нем внятно много говорить. Однако, что я таки про него знаю — это то, что там типизация не только динамическая, но и свободная (loose). Свободная в том смысле, что типы в выражении всегда по возможности приводятся к чему-то общему. И в PHP можно сделать так:
То есть можно свободно использовать неинициализированную переменную в выражениях.
Питон принципиально отличается тем, что типизация динамическая, но строгая (strict). Если переменной дано значение какого-то типа, то как раз у значения этот тип реально есть, и он не будет автоматически приводиться к любому другому типу. Например аналогичный примеру код на Питоне ругнется exception'ом.
Надо еще сказать, что в Питоне по этому поводу нет никакой догмы вроде "никогда ничего не приводить". Тут скорее руководствуются чисто практическим аспектом: делать то, что логично в данном случае. Так например кое что с None все-таки можно делать. Например, сравнивать с целыми и строками (но не с датой). И для своих собственных типов пользователь тоже может сам определить результаты операция с None.
if not s
это крайне неудачная конструкция - слишком много в мире вещей, которые приводятся к False. Например, нельзя отличитьs=""
иs=None
Так что никуда не денешься, придётся писать
if s==None
Кстати, и в питоне, и в php есть другое решение - совсем не объявлять неинициализированную переменную. Попросту
del
её.if "a" in locals(): // делать что-то с a else: // делать что-то без а
Или даже
try: // делать что-то с a except NameError: // делать что-то без а
Согласен, "
if not s
" может быть неудачной (хотя и не всегда). В любом случае, явная проверка на None ("if s==None
" или даже красивей "if s is None
") все же гораздо лучше, чем проверка на что-то другое.Но вот решения с "in locals()" или exception'ом я бы не приветствовал :-). Во-первых просто громоздко, а во-вторых они, опять-таки, уводят от прямого сообщений "переменной нет" к некой ширме "это означает, что переменной нет".
В PHP действительно нет строгой типизации. Имея какой-никакой опыт в программирповании думаю, что это чаще плохо, чем хорошо. Поскольку оболтусы называющие себя Программистами, даже не догадываясь что существуют некие понятия о хорошем и плохом стиле программирования, выдают такой грязный код, который работает только до первого внесения изменений в него. И отсутствие строгой типизиции в PHP усугубляет ситуацию. По поводу же значений Null и их поведения в операциях в PHP - все сказано в документации. Нужно иногда ее читать. Это особенность, о которой все известно, ну или почти все :). А кому неизвестно - это его проблемы. Тем более, что приведенный пример ярко демонстрирует возможности языка и небезопасный стиль программирования. Если полагаться только на типизацию, то человеческий фактор все равно может сыграть свою негативную роль.
P.S. Если переменной присвоено значение null - она, строго говоря, инициализирована. Ее можно проверить is_null(). И существование переменной тоже можно проверить. Это работа программиста.
Наоборот :) Присваивание None/null это не более, чем конвенция, может быть, более удобная, чем присваивание -1. Если переменной нет - пусть её не будет и в namespace. Ну да, in locals() это немного уродливо, но ведь никто не мешает сделать функцию.
В php из-за его свободной типизации этот вариант даже предпочтительней: если $n не объявлено,
echo $n +1;
просто вылетит, проверка isset() абсолютно прозрачна.Конечно конвенция. Но это стандартизованная конвенция, и потому всем очевидная. Поэтому-то мне и кажется, что менее читаемые варианты все же ничем не выигрывают.
P.S. Как приятно — такая маленькая тема, а так подробно в комментариях раскрывается :-). Большое всем спасибо!
По моему, инициализировать переменные нужно всегда. И инициализировать их так, что б не было больно при возможных проблемах. Будущий массив инитить пустым массивом, строку - пустой строкой, int - нулем, коэффициент - 1, и так далее...
Тогда не будет проблем и в php с его приведением типов, и в python.
А как, тогда, насчёт такого примера (как уже упомянул CR):
$i=5;
unset ($n):
echo $i +$n; // выдаст 5 и ругнется на неинициализированную переменную
Проверять $n на никакое значение можно так:
if (isset($n)) {...} else {...} // isset() не ругается а молча делает своё дело
Лучше так:
echo (isset($n))?$i+$n:"blabla";
Ну что же, давайте пофлеймим.
а) чего -то я не пойму а если в Java делается return ; то что же возвращается? Не иначе как тот самый Null только в "обертке". Так что причем тут Питон не пойму.
б) - ну и про наше любимое В Руби вообще нельзя использовать переменную до ее определения (если только это не свойство обьекта - тогда оно будет nil)
а поскольку в контексте функции доступны методы и переменные то первая же попытка использовать "ни то ни другое" вызовет ошибку подобную питоновской.
А дальше вот начинается типа правильная штука.
Любой обьект отвечает на простейшее сообщение nil?
причем это совершенно не то же самое что empty($var). Потому что для этого есть "empty?" (но на него не отвечает nil)! Более того, когда например массив очищается от nil'ов методом compact! ваш собственный обьект тоже спросят "а не nil? ли ты?"
Дабы получить традиционное PHP-поведение в рельсах добавлен метод blank?
По поводу "красиво читаются" это даааа.... (я очень полюбил unless, until и пр.)
Поскольку Рубин довольно строгий в булях ВСЕ кроме false и nil приводится к true. А вот в PHP с этим забавно (и неприятно) потому что к false приводятся и пустой массив, и пустая строка и integer-ноль... что messy и подкладывает засаду
Согласен, только я всегда использую "if foo is [not] None", даже когда в этом нет прямой необходимости (как в случае с датами). Это ИМХО делает код яснее.
Кому как. Мне лично - "приятно". Потому что часто это позволяет сэкономить код. А чтобы не было засад, нужно пользоваться операторами === и !==
Вообще то для языков со статической типизацией, читеем Фаулера и его NullPattern.