Из серии "за что мне нравится Python"... Сегодня сформулировал у себя в голове еще одну приятную штуку.

Часто бывает нужно, чтобы у переменной какого-то типа было "никакое" или "неопределенное" значение. Скажем, еще непосчитанное количество, заданное int'ом или время создания объекта, который еще до конца не создан, заданное timestamp'ом.

В языке со статическим типизированием у элементарных типов (целое, дата/время) таких специальных значений нет, и чтобы это обойти есть, в общем-то, всего два подхода:

А что же нам предлагает Python. Поскольку у переменной нет привязки к типу, то любой переменной в любой момент можно назначить значение None (аналог NULL'а, Nil'а). А потом, когда надо, присвоить ей нужное реальное значение. В итоге:

Вот так все логично и лаконично.

Комментарии: 14

  1. uncle.f

    Не буду разводить здесь Вашу нелюбимую дискуссию, но мне за тот же null (и не только) нравится PHP. :-)

  2. Иван Сагалаев

    Оне не моя нелюбимая :-). Я просто не знаю PHP настолько близко, чтобы о нем внятно много говорить. Однако, что я таки про него знаю — это то, что там типизация не только динамическая, но и свободная (loose). Свободная в том смысле, что типы в выражении всегда по возможности приводятся к чему-то общему. И в PHP можно сделать так:

    $i=5;
    $n=null;
    echo $i + $n; //выдаст 5
    

    То есть можно свободно использовать неинициализированную переменную в выражениях.

    Питон принципиально отличается тем, что типизация динамическая, но строгая (strict). Если переменной дано значение какого-то типа, то как раз у значения этот тип реально есть, и он не будет автоматически приводиться к любому другому типу. Например аналогичный примеру код на Питоне ругнется exception'ом.

    Надо еще сказать, что в Питоне по этому поводу нет никакой догмы вроде "никогда ничего не приводить". Тут скорее руководствуются чисто практическим аспектом: делать то, что логично в данном случае. Так например кое что с None все-таки можно делать. Например, сравнивать с целыми и строками (но не с датой). И для своих собственных типов пользователь тоже может сам определить результаты операция с None.

  3. CR

    if not s это крайне неудачная конструкция - слишком много в мире вещей, которые приводятся к False. Например, нельзя отличить s="" и s=None

    Так что никуда не денешься, придётся писать if s==None

    Кстати, и в питоне, и в php есть другое решение - совсем не объявлять неинициализированную переменную. Попросту del её.

    if "a" in locals(): // делать что-то с a else: // делать что-то без а

    Или даже
    try: // делать что-то с a except NameError: // делать что-то без а

  4. Иван Сагалаев

    Согласен, "if not s" может быть неудачной (хотя и не всегда). В любом случае, явная проверка на None ("if s==None" или даже красивей "if s is None") все же гораздо лучше, чем проверка на что-то другое.

    Но вот решения с "in locals()" или exception'ом я бы не приветствовал :-). Во-первых просто громоздко, а во-вторых они, опять-таки, уводят от прямого сообщений "переменной нет" к некой ширме "это означает, что переменной нет".

  5. Mike

    В PHP действительно нет строгой типизации. Имея какой-никакой опыт в программирповании думаю, что это чаще плохо, чем хорошо. Поскольку оболтусы называющие себя Программистами, даже не догадываясь что существуют некие понятия о хорошем и плохом стиле программирования, выдают такой грязный код, который работает только до первого внесения изменений в него. И отсутствие строгой типизиции в PHP усугубляет ситуацию. По поводу же значений Null и их поведения в операциях в PHP - все сказано в документации. Нужно иногда ее читать. Это особенность, о которой все известно, ну или почти все :). А кому неизвестно - это его проблемы. Тем более, что приведенный пример ярко демонстрирует возможности языка и небезопасный стиль программирования. Если полагаться только на типизацию, то человеческий фактор все равно может сыграть свою негативную роль.
    P.S. Если переменной присвоено значение null - она, строго говоря, инициализирована. Ее можно проверить is_null(). И существование переменной тоже можно проверить. Это работа программиста.

  6. CR

    уводят от прямого сообщений “переменной нет” к некой ширме “это означает, что переменной нет”.

    Наоборот :) Присваивание None/null это не более, чем конвенция, может быть, более удобная, чем присваивание -1. Если переменной нет - пусть её не будет и в namespace. Ну да, in locals() это немного уродливо, но ведь никто не мешает сделать функцию.

    В php из-за его свободной типизации этот вариант даже предпочтительней: если $n не объявлено, echo $n +1; просто вылетит, проверка isset() абсолютно прозрачна.

  7. Иван Сагалаев

    Конечно конвенция. Но это стандартизованная конвенция, и потому всем очевидная. Поэтому-то мне и кажется, что менее читаемые варианты все же ничем не выигрывают.

    P.S. Как приятно — такая маленькая тема, а так подробно в комментариях раскрывается :-). Большое всем спасибо!

  8. Elf

    По моему, инициализировать переменные нужно всегда. И инициализировать их так, что б не было больно при возможных проблемах. Будущий массив инитить пустым массивом, строку - пустой строкой, int - нулем, коэффициент - 1, и так далее...
    Тогда не будет проблем и в php с его приведением типов, и в python.

  9. uncle.f

    $i=5;
    $n=null;
    echo $i + $n; //выдаст 5

    А как, тогда, насчёт такого примера (как уже упомянул CR):

    $i=5;
    unset ($n):
    echo $i +$n; // выдаст 5 и ругнется на неинициализированную переменную

    Проверять $n на никакое значение можно так:

    if (isset($n)) {...} else {...} // isset() не ругается а молча делает своё дело

  10. Elf

    Лучше так:
    echo (isset($n))?$i+$n:"blabla";

  11. Julik

    Ну что же, давайте пофлеймим.

    а) чего -то я не пойму а если в Java делается return ; то что же возвращается? Не иначе как тот самый Null только в "обертке". Так что причем тут Питон не пойму.

    б) - ну и про наше любимое В Руби вообще нельзя использовать переменную до ее определения (если только это не свойство обьекта - тогда оно будет nil)

    class Dummy
       def value
            @val
       end
    end
    
    Dummy.new.value # вернет nil
    

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

    А дальше вот начинается типа правильная штука.

    Любой обьект отвечает на простейшее сообщение nil?

    nil.nil? # true
    3.nil? # false
    

    причем это совершенно не то же самое что empty($var). Потому что для этого есть "empty?" (но на него не отвечает nil)! Более того, когда например массив очищается от nil'ов методом compact! ваш собственный обьект тоже спросят "а не nil? ли ты?"

    Дабы получить традиционное PHP-поведение в рельсах добавлен метод blank?

    nil.blank? # true
    ''.blank? # true и тд
    '      '.blank? # true, ага?
    'foo'.blank? # false
    

    По поводу "красиво читаются" это даааа.... (я очень полюбил unless, until и пр.)

    @creation_date = Date.today unless @creation_date
    

    Поскольку Рубин довольно строгий в булях ВСЕ кроме false и nil приводится к true. А вот в PHP с этим забавно (и неприятно) потому что к false приводятся и пустой массив, и пустая строка и integer-ноль... что messy и подкладывает засаду

  12. Max Ischenko

    Согласен, только я всегда использую "if foo is [not] None", даже когда в этом нет прямой необходимости (как в случае с датами). Это ИМХО делает код яснее.

  13. uncle.f

    А вот в PHP с этим забавно (и неприятно) потому что к false приводятся и пустой массив, и пустая строка и integer-ноль… что messy и подкладывает засаду

    Кому как. Мне лично - "приятно". Потому что часто это позволяет сэкономить код. А чтобы не было засад, нужно пользоваться операторами === и !==

  14. Stoune

    Вообще то для языков со статической типизацией, читеем Фаулера и его NullPattern.

Добавить комментарий