Python: надежная защита от потери запятой в вертикальном списке строк

    Списки строк в программах встречаются часто. Для удобства чтения их не менее часто форматируют вертикально, по одной строке. И есть в такой конструкции уязвимость — если при изменении списка потерять запятую между элементами, то многие языки просто склеют строки слева и справа от пропущенной запятой — в результате получится валидный с точки зрения языка список, в котором на один элемент меньше чем ожидается и один элемент имеет некорректное значение. Есть много способов профилактики этой проблемы, но недавно на stackoverflow мне показали настолько простой и надежный способ, что я просто не могу им не поделиться.



    Демонстрация проблемы



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

    languages = [
    	"english",
    	"russian",
    	"italian" # Печалька. Такое иногда бывает.
    	"spanish" ]
    


    Если внимательно посмотреть, между строками «italian» и «spanish» пропущена запятая. Но при запуске такой программы ошибок не будет: Python просто склеит строки «italian» и «spanish», превратив наш список вот в это:

    languages = [ "english", "russian", "italianspanish" ]
    


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

    Как бороться по-феншую



    В соответствии c феншуем, данный ряд проблем необходимо отсекать статическими анализаторами кода типа lint в рамках автобилда. Но тут есть неприятный нюанс — pylint по умолчанию не считает пример выше ошибочным. Следовательно, придется его долго и муторно настраивать, потому как есть много вполне корректного кода, где строки склеиваются по делу и никаких запятых быть не должно. Плюс не у всех настроена связка pylint + autobuild, а поднимать полноценный continous integration с нуля только ради указанной проблемы не всегда с руки.

    Как борются на практике



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

    languages = [
    	"english",
    	"russian",
    	"italian",
    	"spanish", # Если переместить этот элемент вверх, запятые не поломаются.
    ]
    


    Минусом первого способа является то, что он защищает только от ошибок копипасты — но не защищает от опечаток и результатов применения к тексту скриптов.

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

    languages = [
    	  "english"
    	, "russian"
    	, "italian"
    	, "spanish"
    ]
    


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

    Новый способ



    Был подсказан гуру на stackoverflow. Не могу сказать что он особо красив или удобен — но он прост и надежен. При потере запятой случается ошибка выполнения скрипта. Способ заключется в окружении каждой строки круглыми скобками — это превращает epression типа строка в сложносоставной expression, который уже склеивать нельзя:

    languages = [
    	("english"),
    	("russian"),
    	("italian") # Теперь потерянная запятая - синтаксичекая ошибка.
    	("spanish") ]
    


    Вот такое неожиданное решение. Надеюсь, послужит кому-нибудь источником вдохновения. Приятных выходных, коллеги :)
    Метки:
    Фаматек 63,47
    Компания
    Поделиться публикацией
    Реклама помогает поддерживать и развивать наши сервисы

    Подробнее
    Реклама
    Похожие публикации
    Комментарии 161
    • –6
      А может просто вдумчиво писать программы? В С++ тоже есть волшебный оператор "==" и его младший брат "="… и ничего, тонны кода работают же!
      • +23
        Эти гуру и до этих операторов добрались:

        Советуют писать так, чтобы не перепутать = и ==
        if (7 == x) { ... }


        А для сравнения переменных можно и так написать
        if (x+0 == y) { ... }

        • +4
          Строго говоря, сейчас это уже не так актуально как десять-пятнадцать лет назад. Актуальные компиляторы (Visual Studio 2005+, XCode, gcc под андроид) уже научились с грехом пополам выдавать варнинги в таких случаях. Главное — уровень варнингов задирать повыше и писать аккуратно, чтобы других варнингов не быо :).
        • +12
          Как хорошо, что ни разу в рабочем коде я этих x+0 не видел. Такая дикость!
          CERT кстати рекомендует вообще отказываться от присваиваний в условиях, тогда подобные ошибки «на глаз» сразу видны.
          • +1
            ну в си отказаться от присваивания в условиях(особенно циклов) — потеряем кучу вкусных плюшек
          • +3
            Ещё в java что бы строку не проверять на null гуру делают так
            if ("five".equals(someString) {
                doSomething();
            }
            
        • +48
          для С++ и волшебного оператора '==' давно придумали условие Мастера Йоды:



          Вдумчиво писать программы можно. Разрешаю :).
          • +3
            Вдумчиво писать никто не запрещает, но иногда случаются ошибки.
            • +3
              Я не кармадрочер, уже давно привык, что ее нет… Но почему блин если у комментария -3, то и кармы стало на 3 меньше, не поленились и тыкнули, а когда твой коммент набирает 120 плюсов, карма хер сдвинется.
              • +4
                Это на курсы по теории организации социальных сетей. Там вагон теорикрафта и много неочевидных взаимосвязей. Вообщем не парьтесь, все так и должно работать. И с этим надо работать — это данность, ее нельзя поменять :).
            • +19
              Мне кажется, костыль не стоит свеч. Ошибка вывалится на стадии компиляции или обнаружится редактором. А смотреть и править такой изврат не очень приятно.
              • +2
                Вы сейчас о чем? Основная проблема как раз и заключается в том, что интерпретатор Python не считает это ошибкой.

                Редактор, это, конечно, хорошо. Но программа часто несколько боьше по объему, нежели тривиальная домашняя страничка, в этот редактор влезающая. В случае массовой правки больших объемов регекспами/рефакторингом (а это большой такой источник ошибок) на двухсотом диффе глаз уже замылится и никакой редактор не спасет.
                • +2
                  Если у вас большие списки строчек в программе, которые вы еще и правите вручную, велика вероятность что с вашей программой уже что-то не так. Например переводы как вы поддерживать собираетесь?
                  • 0
                    Переводы чего? Имен JSON запросов? Расширений файлов? Списков файлов? Списков языков или локалей?

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

                    Есть ряд сценариев, где указанный метод применим. Перечисление таких сценариев выходит за рамки моей заметки :)
                    • +1
                      В небольших скриптах и руками можно отловить 1-2 ошибки которые возникнут, быстрее чем скобочки везде расставлять :) А в production'е надо еще всех в команде этому методу научить, а оно вряд ли кому надо.
                      Сам по себе метод интересный, но есть подозрение, что на практике малоприменимый.
                      Если много текстовых констант лучше уж их просто в отдельный файл положить без всяких запятых например одна константа на строчку.
                      • +1
                        В небольших скриптах и руками можно отловить 1-2 ошибки которые возникнут, быстрее чем скобочки везде расставлять :)


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

                        В целом применимость конечно нужно оценить. Я свой отдел на такой способ перевел — через годик можно будет сравнить эффект.
                        • 0
                          Может попробуете с внешними файлами для констант? Это как то более удобное и красивое решение на мой взгляд, и регэкспами редактировать удобнее, все таки надо разделять данные и код :)
                          • +2
                            Для внутренних скриптов автоматизации вынесение ряда данных из кода — это усложнение на пустом месте.

                            BTW, никто же не говорит о том, что данный способ красив, удобен и приминим везде. Ниша у него довольно специфическая. Статья о том, что такой способ есть. А применять ли его, и если применять то где — это ваше личное дело. В программировании вообще можно разными способами задачи решать. Так что я тут ассортимент демонстрирую, а не способ пропагандирую. Можно и внешние файлы для констант. Разных способов вообще много.
                            • +1
                              Я то не против, моя мысль была такая, что если констант мало то проблема не особо актуальна, а если много то лучше их вынести в ресурсные файлы или DB.
                              • +1
                                Как и с любым нишевым костылем нужно знать весь ассортимент, чтобы применить наиболее оптимальное решение в каждом конкретном месте.
                  • +1
                    Действительно, сколько работаю с питоном особо не сталкивался с этим. Могу предложить хранить такие данные в отдельных *.json файлах, где подобные приколы будут пресечены на месте парсером. Я для бэкендов храню их в отдельном git репозитории и подключаю сабмодулем к основному проекту.
                    • 0
                      И в json тоже хранится, и в SQLite, и DSL генерится — но случаев разных, к сожалению, много. И часто список нужен здесь и сейчас, код внутренний, json конструкцией не предусмотрен… А тут такой трюк.
                    • 0
                      AS3, однако, считает пропущенную запятую в точно таком же массиве ошибкой.
                      Имхо, такие вещи в Питоне наводят меня на мысль держаться от него в стороне. Пропуск подобных вещей напоминает мне некоторые браузеры (IE), которые хавают любой кривой HTML без вопросов и выводят из него то, что считают нужным. Хорошо, что есть strict mode и XHTML.
                      Как бы я решал эту проблему, если бы она у меня возникла — написал бы простенький лексический анализатор для отлова пропущенной запятой в массиве и прогнал весь код через него.
                      Может, кто-нибудь накидает, кстати?
                      • 0
                        Все с ног на голову. Питон пропускает две строки без запятой не потому, что кто-то не доглядел, когда писался парсер. А потому что в нем есть такой синтаксис: две строки, написанные друг за другом склеиваются в одну на этапе парсинга. Это удобно в ряде случаев, когда строка слишком длинная и её нужно разбить на несколько строчек в исходном тексте.
                        • 0
                          Да я верю, что это придумано для удобства, чтобы не ставить плюс, что в ряде случаев это удобно и так далее. Вот только это породило конкретную проблему у автора топика. Которую он предлагает решить, обрамив каждую строку скобками. В результате, чтобы не ставить один плюс, мы ставим две скобки — супер!
                          • 0
                            Да я верю, что это придумано для удобства, чтобы не ставить плюс, что в ряде случаев это удобно и так далее. Вот только это породило конкретную проблему у автора топика. Которую он предлагает решить, обрамив каждую строку скобками. В результате, чтобы не ставить один плюс, мы ставим две скобки — супер!
                            Я предложил решение, которое исправляет эту проблему. С помощью создания утилиты на любом языке и прикручивания ее в дев цикл автор может сохранить код в чистоте и избавить программистов от лишних движений. Вот только Питон от этого лучше не станет. Лучше бы у него был какой-нибудь strict mode, или warning бы выдавался.
                            • 0
                              > В результате, чтобы не ставить один плюс, мы ставим две скобки — супер!
                              Не мы, а автор. И дело не в плюсе, а в приоритете операций. + довольно низкоприоритетная операция, а слияние строк на этапе парсинга — наивысший приоритет. В результате:
                              'some string' 'another'.startswith('some') == True
                    • +14
                      Пожалуйста, никогда так не делайте, если ваш код может читать кто-то другой.
                      • –17
                        Спасибо, ваше мнение очень важно для нас :).
                        • +12
                          Как раз думал, что же вы ответите.
                      • +2
                        Если очень хочется, то хотя бы:
                        languages = """english
                        russian
                        italian
                        spanish""".split()
                        
                        • 0
                          Если в строке случится пробел — то будет проблема :).
                          • +1
                            split("\n")
                            

                            ?
                            • 0
                              Если в строке случится перенос строки…

                              Это общая проблема разделителя. Простого решения у нее нет, любое простое решение — это бомба замедленного действия до первого неудачного регекспа по коду. А сложное решение неэффективно — слишком много строчек кода непонятно зачем :).
                            • +1
                              Можно разделить по переводу строки.
                              • 0
                                Можно. И тогда первый же регексп который добавит перевод строки в одну их строк списка разрушит цивилизацию :).

                                Это общая проблема разделителя. Простого решения у нее нет, любое простое решение — это бомба замедленного действия до первого неудачного регекспа по коду. А сложное решение неэффективно — слишком много строчек кода непонятно зачем :).
                            • 0
                              В функции/методе такие многострочники выглядят ужасно.
                              • 0
                                Согласен, но это иногда уместно и не вызывает вопросов, в отличии от предложенного решения.
                              • –1
                                За такой код надо увольнять.
                                Редкостный бред.
                                Писать надо сразу правильно, а главное помнить одно «есть только один способ правильно сделать что-то» в Питоне,
                                и стоит ему следовать.
                                Вы сейчас показали хрень.
                                • –2
                                  Правда я молодец? :)

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

                                  За такой код не надо увольнять — это хороший код, если применен к месту.

                                  Ну и кому из нас верить?
                                  • +2
                                    Ваша статья как раз познавательна об особенностях Питона, а я имел ввиду
                                    то что написал hamst.
                                    Каждый раз чтобы интерпретатор делал сплит по статическому тексту чтобы превратить это в список? это за гранью добра и зла.
                                    • +2
                                      Мои извинения — синие точки слева не очень помогают понять кто кому ответил О_О. Бывает :).
                                      • –9
                                        Ну питон сам по себе тормозной так что погоды не сделает имхо, главное чтоб не в цикле :)
                                    • +1
                                      Мотивируйте, пожалуйста, почему это бред/хрень?
                                      • +2
                                        Потому что, при вызове ф-ии или инициализации, интерпретатор будет разбирать вашу строку и делать из нее список.
                                        Проще явно правильно написать один раз.
                                        • 0
                                          Т.е. основной момент — производительность, согласен, но хочу сказать, что там, где пишут такой код, идут на это сознательно.
                                          Решений, в которых удобство поддержки длинных списков важнее скорости, не так уж и мало, к примеру, я активно использую такой подход в системах сборки на scons в небольших проектах для редактирования списка исходников. Сопровождать оформленые таким образом списки мне сильно удобней.
                                    • +1
                                      Этот способ плох тем, что нельзя комментировать строки или отключать некоторые через закомментирование.
                                      Хотя (на примере не питона)…
                                      string languages = @"english
                                      russian
                                      " /* это типа итальянский -> */ +@"italian
                                      spanish".Split("\n");
                                      
                                      • 0
                                        Тогда уже сплит по \n, а то вдруг в строке будет пробел. Но это все равно бред так делать :))
                                    • +3
                                      eyeofhell, спасибо за статью, но овчинка выделки не стоит. Конструкция выглядит очень неестественно для Python. Если пишется маленький скрипт, то такую ошибку поймать не сложно. А если проект большой, то уж лучше pylint, тесты и т.д.
                                      • +2
                                        Я и не претендую на то, что это очередная серебряная пуля. И отдельно подчеркнул, что не считаю данный способ особо красивым и удобным. У него плюсы в надежности.

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

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

                                        Опять же, может в комментах чего дельного напишут. Питон для меня не то чтобы очень профильная технология, есть возможность узнать что-нить новое и интересное малой кровью
                                      • +2
                                        Питона не знаю, но языков, которые бы не ругнулись на изначальном примере — не встречал
                                        • +1
                                          Ruby?

                                          [ 'a', 'b' 'c' ] == [ 'a', 'bc' ]
                                          
                                          • +1
                                            Попробуйте как в примере сделать:
                                            [ 'a',
                                             'b'
                                             'c' ] == [ 'a', 'bc' ]
                                            
                                            • 0
                                              Да, забавно — тут синтаксис у питона и руби отличается. Вот и первый профит от статьи — такого нюанса я не знал.
                                          • +4
                                            С\C++ хотя бы:

                                            char * data[] = { 
                                               "str1",
                                               "str2"
                                               "str3",
                                               "str4"
                                            };
                                            


                                            Вполне себе рабочий код. Варнинги, конечно, кидает, но ошибки компиляции нет будет.
                                            • 0
                                              Тут хотя бы варнинги. В профессиональной разработке задирают до 4-го уровня и правят все что вылезает, так что в плюсах оно не так страшно.
                                              • 0
                                                Извиняюсь, не туда посмотрел. Варнингов не будет, даже с 4-ым уровнем.
                                                • 0
                                                  Печалька :(.
                                                  • 0
                                                    Квалификатор const все-таки забыли
                                                    • 0
                                                      много кода.

                                                      BTW, насколько я помню стандарт на память, строковые литералы по умолчанию const — они физически расположены в ресурсах исполняемого модуля, которые при его загрузке в память мапяться в readonly сегмент O_O.
                                                      • 0
                                                        Без ключа -Wno-write-strings (g++), скомпилуется с ворнингами
                                                • –1
                                                  а кто мешает написать хотя бы
                                                  char* data[4] = { ... }
                                                  • +4
                                                    Если я, опять же, правильно помню стандарт на память, но инициализация массива списком инициализации, если список короче заявленного количества элементов, приведет к тому, что в недостающие элементы будут записаны нолики :(. Проблему это ни коим образом не решит.
                                                • 0
                                                  Clojure.
                                                  (def a ["qwe",
                                                          "asd"
                                                          "zxc",
                                                          "rty"])
                                                  
                                                  (def b ["qwe"
                                                          "asd",
                                                          "zxc"
                                                          "rty"])
                                                  
                                                  (= a b) => true
                                                  
                                                  • +3
                                                    А все потому что запятая — сахар
                                                • –15
                                                  Еще один рецепт забыли! — уйти с питона на руби :)
                                                  • 0
                                                    Боюсь, там та же проблема. И даннный костыль, кстати, тоже применим. У Python и Ruby синтаксис похож.

                                                    BTW, вынесу ка я это в заголовок. Чтобы рубисты не пропустили.
                                                    • 0
                                                      Там нет такой проблемы.
                                                      # В одну строку объявлять можно так:
                                                      %w(str1 str2 str3) 
                                                      # Когда в столбик:
                                                      ['str1',
                                                       'str2'
                                                       'str3'] # Синтаксическая ошибка
                                                      

                                                      Да и вообще, в руби строки так редко объявляют, там используют символы:
                                                      [:str1, :str2, :str3]
                                                      
                                                      • +3
                                                        Да, забавно — тут синтаксис у питона и руби отличается. Вот и первый профит от статьи — такого нюанса я не знал.
                                                        • +3
                                                          Что-то вы повторяетесь. Все профиты первые.
                                                          • +5
                                                            Атож. Я загадочен, непредсказуем и владею копипастой :).
                                                      • +1
                                                        Тут нет такой проблемы, дико что эту ситуацию питон не воспринимает как ошибку.
                                                      • +2
                                                        Во, начали душить питонщики, не любят троллинга змеюки :)
                                                        • +1
                                                          Если б было на что переходить, ruby это каменное дитя питона и слона…
                                                          • +1
                                                            Не будьте букой, это просто троллинг :) я вот смотрю какой язык, такие и люди: питон (относительно строгий язык) — и руби (раздолбайский, т.е. можно все).
                                                            P.S. Руби уже быстрее питона :)
                                                            • +1
                                                              Где он быстрее?
                                                              • 0
                                                                Не могу найти где я это читал, но можете в гугле поискать тесты Ruby 1.9.3
                                                                  • 0
                                                                    python3 используется 3 монахами.
                                                                    • 0
                                                                      Вполне таки себе используется.
                                                                      Я уже его использовал, использовал бы больше да gevent пока еще не перешел на него.
                                                                      • –1
                                                                        есть мнение, что gevent зло
                                                                        • 0
                                                                          Откуда такие мнения? Любопытства ради.
                                                                          • 0
                                                                            то, как это реализовано т.е. — хак
                                                                        • –1
                                                                          бхахахах))
                                                                          есть мнение, что ты — зло)
                                                                      • 0
                                                                        нифига. очень даже используется. мэйнтстрим активно переходит (вот даже джанга на днях зарелизится с поддержкой 3 ветки)
                                                                        • 0
                                                                          Иначе говоря, самый популярный фрэимворк (а так flask) все еще не поддерживает 3 ветку. gevent (которая 100500 раз лучше ноды) не поддерживает. Вакансий на 3 ветку нет. Не вижу мэинсттрима. Большая часть библиотек все еще не может в python3. О каком переходе мы говорим?
                                                                          • 0
                                                                            gevent в новом релизе на libev ее поддерживает.
                                                                            bottle поддерживает.
                                                                            И какая часть библиотек не поддерживает python3?
                                                                            greenlet поддерживают. Остальная часть спокойной сработает 2to3.py кроме некоторых редких экземпляров.
                                                                            • 0
                                                                              pylons и pyramid давно поддерживают.
                                                                              джанга с весны(пока в альфе, релиз на подходе)
                                                                              я уже больше года замечательно живу с третьим. проблемм с библиотеками почти нет.
                                                                              • 0
                                                                                Угу, ждём когда Армин Роханер Werkzeug и Flask на Py3 переведут. А всё идёт к тому, что он это сделаёт одним из последних :(
                                                                        • +1
                                                                          1) python2 все ещё быстрее чем python3 (как минимум в тестах на том же shootout)
                                                                          2) а куда блин подевался python2 из shootout?!?
                                                                          3) я не монах)
                                                                          4) 3.3 python имхо вызовет шквальный переход.
                                                                          • 0
                                                                            Почему 3.3 вызовет шквальный переход? Какие у него киллер плюшки?
                                                                            • +1
                                                                              Почитайте ченджлог же

                                                                              Моё имхо:
                                                                              1) перейти с 2.х проще на 3.3 чем на 3.2 и уж тем более чем на 3.1
                                                                              2) оно наконец не медленнее 2го питона практически. Понятно что в некоторых случаях и быстрее
                                                                              3) оно наконец без ucs2/ucs4 сплита юникода
                                                                              4) море синтаксического сахара, наподобие raise from None

                                                                              3й уже даже в генте ворочается как системный питон без проблем. Всё. Пора)
                                                          • +2
                                                            perl выдаёт ошибку, и спасибо ему за это.
                                                            Правда, я всё равно использую первый способ, запятые в каждой строчке — так проще местами менять.
                                                            • 0
                                                              И не будет лишних записей в истории, в случае работы через git и добавления новых строк.
                                                            • 0
                                                              groovy выдает ошибку и спасибо ему за это (С)
                                                              • 0
                                                                Именно из-за таких штук придумали языки со строгим синтаксисом и типизацией такие как Паскаль, Ява, C#,…
                                                                • 0
                                                                  А можно просто покрывать всё тестами, и не страдать от строгого синтанксиса и типизации.
                                                                  • 0
                                                                    т.е. покрывать тестами то, что должен делать компилятор и система типов — это «не страдать»?
                                                                    • +2
                                                                      Тестами надо покрывать не проверку типов, а бизнес-логику.
                                                                      • 0
                                                                        бизнес-логику надо покрывать тестами в любом случае, причем здесь типизация и синтаксис?
                                                                        • +2
                                                                          При том, что все ошибки в типизации и синтаксисе обнаружатся на этапе запуска тестов.
                                                                        • 0
                                                                          Тесты штука хорошая, но при ограниченном бюджете все что хочется не всегда удается тестами покрыть. Просто потому, что тот бизнес, который за бизнес логику платит деньги, иногда хочет здесь и сейчас :). Приходится балансировать.

                                                                          BTW, не все одинакого удобно покрывается тестами. Тот же скрипт сборки или деплоя — его тестами покрыть и поддерживать в актуальном состоянии задача очень нетривиальная, я покрывал :(.
                                                                          • 0
                                                                            скрипт сборки — сам себе почти тест
                                                                  • 0
                                                                    Расскажите лучше, как защититься от случайного использования строки в контексте, где ожидается список строк.
                                                                    • 0
                                                                      Автоматической защиты я не знаю, в питоне строка — это список символов :). Ну или что-то очень к нему близкое. Можно поставить прямой запрет в API:

                                                                      def myapifunction( self, somelist ) :
                                                                        assert hasattr( somelist, '__iter__' ), "passing string instead of a list?"
                                                                        ...
                                                                      
                                                                      • +1
                                                                        пробелов-то наплодили… )

                                                                        (yandex) mocksoul@mocksoul-notebook ~root % python -m this | grep implicit
                                                                        Explicit is better than implicit.

                                                                        а почему лучше isinstance(x, (list, tuple)), если уж список ожидается.
                                                                        • 0
                                                                          а почему лучше isinstance(x, (list, tuple)), если уж список ожидается.


                                                                          Потому что наличие __iterable__ — признак контейнера типа «коллекция». У строки его нету. См. duck typing.
                                                                      • 0
                                                                        isinstance(x, basestring)? Или я уже ниче не понимаю)
                                                                      • +2
                                                                        Покрываем код тестами.
                                                                        • 0
                                                                          И какой именно тест поможет найти эту ошибку в, например, скрипте юнит-тестов? Или в скрипте развертывания сервера на тестовом окружении? Или в скрепте настройки чего-нибудь? Юнит-тесты — штука дорогая. Покрывать ими, к примеру, утилиты внутренней автоматизации — дорого.
                                                                          • 0
                                                                            Именно утилиты внутренней автоматизации нужно покрывать тестами в первую очередь.
                                                                            Если весь проект ляжет тупо из-за того, что была ошибка в скрипте раскладки (к примеру) — попа будет болеть долго.
                                                                            • 0
                                                                              Покрывать тестами вообще все — дорого. Это можно себе позволить только если деньги или время не лимитированы. При наличии лимита по бюджету и срокам приходится балансировать между запасом прочности сейчас, запасом гибкости на будущее и сроком, к которому это можно собрать.

                                                                              BTW, я разве где-то написал что описанный трюк позволяет отказаться от тестов? Вы так активно меня в этом упрекаете в комментах, что можно заподозрить мое темное альтер-его в том, что оно по-тихому отредактировало пост и написало там какую-нить ересь :).
                                                                            • +1
                                                                              Если проект не будет написан к дедлайну, то попа болеть не будет, но и источника боли тоже не будет. Нет проекта — нет денег, нет работы.
                                                                        • +5
                                                                          Совсем не pythonic-way. Больше похоже на JavaScript.

                                                                          Про PEP-8 автор, конечно, не в курсе, — советую ознакомиться.

                                                                          И проблемой это не является — вкрутить проверку кода на соответствие PEP8 (утилиты "pep8", или даже "flake8", что ещё кошернее) в редактор при сохранении или в git (mercurial) хуком при коммите — дело трёх минут.
                                                                          • +1
                                                                            Чтобы не было голословным:
                                                                            $ cat test.py 
                                                                            languages = [
                                                                                  "english"
                                                                                , "russian"
                                                                                , "italian"
                                                                                , "spanish"
                                                                            ]
                                                                            

                                                                            $ pep8 test.py 
                                                                            test.py:2:7: E121 continuation line indentation is not a multiple of four
                                                                            test.py:2:16: E203 whitespace before ','
                                                                            test.py:3:16: E203 whitespace before ','
                                                                            test.py:4:16: E203 whitespace before ','
                                                                            


                                                                            • 0
                                                                              При чем тут 'whitespace before ','? На приведенном скриншоте — три ложных срабатывания на запятую перед литералом и одно неверное форматирование. См. мой пример ниже.
                                                                              • 0
                                                                                При том, что этот код — некорректен с точки зрения pep8. Так писать нельзя.
                                                                                • 0
                                                                                  А вы можете привести пример вертикального списка строк, который пройдет валидацию PEP8? :)
                                                                                  • +1
                                                                                    languages = ["english",
                                                                                                 "russian",
                                                                                                 "italian",
                                                                                                 "spanish"]
                                                                                    


                                                                                    languages = [
                                                                                        "english",
                                                                                        "russian",
                                                                                        "italian",
                                                                                        "spanish",
                                                                                    ]
                                                                                    
                                                                                    • 0
                                                                                      Ну вот у меня pep8 для

                                                                                      languages = [
                                                                                          "english",
                                                                                          "russian",
                                                                                          "italian",
                                                                                          "spanish",
                                                                                      ]
                                                                                      


                                                                                      и

                                                                                      languages = [
                                                                                          "english",
                                                                                          "russian",
                                                                                          "italian"
                                                                                          "spanish",
                                                                                      ]
                                                                                      


                                                                                      показывает одинаковый набор ошибок. Тоесть факта потери запятой в упор не видит. Назовите, чтоли, операционку и версию питона на которой я могу это чудо лицезреть? Или может какой волшебный ключик ему скормить надо?
                                                                                      • –1
                                                                                        Это не потеря запятой. Это не ошибка. Это базис, основа языка Python. Почитайте что-нибудь про язык, прежде чем начинать на нём писать, или возвращайтесь на Си или переходите на раби-перл-что-там-ещё — вам так проще будет.

                                                                                        EDIT: OS X 10.8.2, Python 2.7.3
                                                                                        • 0
                                                                                          То есть вы считаете, что если программист при написании вертикального списка опечатался и не поставил запятую (а в реальном мире такое иногда случается) — то это не ошибка? :)

                                                                                          Ошибка — это когда программа неправильно работает. То, что правила конкатенации строковых литералов в питоне не делают написанное синтаксической ошибкой — не значит, что это вообще не ошибка и с такими опечатками не надо бороться :).

                                                                                          Пишите еще, с интересом читаю вашим комменты :)
                                                                                          • –1
                                                                                            То есть если программист вместо — поставил + это ошибка парсера, вм, компилятора, а не программиста?
                                                                                            Это я к тому, что Питон все по правилам делает, а программист пытается навязать свое в данном посте.
                                                                                            • 0
                                                                                              Я прямо даже не знаю что написать. На тролля вы не похожи, а такой набор слов ответили :(.

                                                                                              Если программист вместо '-' поставил '+', то это логическая ошибка в программе. Логическая ошибка от синтаксической отличается тем, что с точки зрения языка программа валидна и может работать, но работает не так как нужно разработчику. А так, как он написал :)

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

                                                                                              Питон все делает правильно, и в этом печалька. Потому как программисту в данном конкретном случае не нужно чтобы питон что-нибудь делал — ему нужно обнаружить и исправить ошибку. Описанные в статье подходы, начиная от статического анализатора и заканчивая волшебными круглыми скобочками это позволяют сделать.
                                                                            • 0
                                                                              И проблемой это не является — вкрутить проверку кода на соответствие PEP8 (утилиты «pep8», или даже «flake8», что ещё кошернее) в редактор при сохранении или в git (mercurial) хуком при коммите — дело трёх минут.


                                                                              И как эта проверка будет работать?

                                                                              $ pypm install pep8
                                                                              $ echo "l = [ 'a'," > test.py
                                                                              $ echo "  'b'" >> test.py
                                                                              $ echo "  'c' ]" >> test.py
                                                                              $ echo "" >> test.py
                                                                              $ pep8 test.py
                                                                              .\test.py:1:21: E231 missing whitespace after ','
                                                                              .\test.py:5:2: W292 no newline at end of file
                                                                              


                                                                              Здесь, насколько я вижу, только ошибочное срабатывание под windows детектирования newline. Можете привести рабочий пример срабатывания валидатора на проблемный код в топике?
                                                                              • 0
                                                                                А это не ошибочный код — с точки зрения питона код в топике валидный — я сам так разбиваю строки, когда они слишком длинные. Поэтому ни один валидатор не поймёт, корректен ли этот код, или это ощибка кодера.

                                                                                ИМХО, проблема высосана из пальца, и решение просто ужасно.

                                                                                Вот честно, — ни разу вообще не сталкивался на практике с такого рода ошибками, а из-за невнимательности разработчика можно наделать гораздо более серьёзные ошибки.
                                                                                Код таких неопытных программистов я всегда (!) прогояю через code-review — в таком случае выявляются 99% таких (и всех других прочих) опечаток.

                                                                                Ну и тесты, тесты, тесты, тесты, тесты.
                                                                                • –4
                                                                                  Вот честно, — ни разу вообще не сталкивался на практике с такого рода ошибками


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

                                                                                  Программирование — оно большое. И между написанием веб странички и созданием драйвера ядра довольно мало общего, хотя и то и другое «программируется программистами на кампутере».

                                                                                  Если бы проблема была высосана из пальца — стал бы я о ней писать? Закономерность была выявлена в последний год, когда в компании активнее стали использовать питон для написания скриптов сборки, тестирования и деплоя. И решение я пополз искать далеко не после первого кейса с такой ошибкой :).
                                                                                  • –1
                                                                                    Скажем так, мой опыт гораздо шире и глубже, чем вам показалось.
                                                                                    Переходить на личности и «умиляться», уподобляясь вам не буду.

                                                                                    P.S. Вот за такой лапшевидный php-style код: bitbucket.org/eyeofhell/sigma/ я бы гнал своего бойца в три шеи. Слава Богу, таких не держим ;)
                                                                                    • –2
                                                                                      Могу только за вас порадоваться и пожелать творческих успехов. Пишите еще, с интересом читаю вашим комменты :). Особенно про лапшу в сигме — то-то я там новую декомпозицию на общей шине сделал. Вот как оказывается лапша-то возникает :).
                                                                                      • –1
                                                                                        Взаимно.
                                                                                        • 0
                                                                                          А ваш код тоже можно почитать? Поделитесь ссылкой, пожалуйста.
                                                                                          • –1
                                                                                            В профиле всё есть.
                                                                                            • +2
                                                                                              первый же файл…

                                                                                              case 'l':
                                                                                                port = atoi(optarg);
                                                                                              case '?':
                                                                                                syslog(LOG_ERR, "Unknown option character")
                                                                                                break;
                                                                                              


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

                                                                                              Владимир — откуда столько агрессии? Я вас чем-то обидел? Написал что-то оскорбляющее ваши профессиональные чувства? Вы тут в каждом комменте пытаете доказать что я не умею программировать. От чего такая реакция на обычную обзорную заметку об интересном и забавном способе борьбы с опечатками? Я же не декларирую что это серебряная пуля и что так надо делать. Просто еще одна возможность, о которой мало кто знает. Для общего развития, так сказать. Программистам же надо развиваться?
                                                                                              • 0
                                                                                                Извините, был взволнован.
                                                                                                • +2
                                                                                                  Бывает. BTW, интересный трюк моего авторства для C и C++. Позволяет избежать потере бряка:

                                                                                                  switch( subject )
                                                                                                  {
                                                                                                  break; case 'a' :
                                                                                                    foo();
                                                                                                  break; case 'b' :
                                                                                                    bar();
                                                                                                  break; default :
                                                                                                    assert( false );
                                                                                                  }
                                                                                                  


                                                                                                  Не стандарт, конечно — но компилируется на всем, до чего я смог дотянуться :).
                                                                                                  • 0
                                                                                                    Интересный приём, спасибо.

                                                                                                    Ошибку в «mrad» поправил, спасибо за замечание.
                                                                                                    • +1
                                                                                                      Выглядит плохо. Не пройдет ни один review по code style.
                                                                                                      • +1
                                                                                                        Я думал, я один тут заморочен на code style, code review, единости и целостности проекта, соответствие его общемировым стандартам и практикам, лёгкостью поддержки и развития посредством расширения команды разработчиков.

                                                                                                        Удваиваю.

                                                                                                        EDIT: не туда написал. ответ на комментарий Semy.
                                                                                                        • 0
                                                                                                          Эта штука сделана не для того чтобы ревью проходить. Она сделана чтобы бряки не терялись :).

                                                                                                          Хотя да, и условие йоды, и это выглядит не лучшим образом. Тут, конечно, лучше настроенный lint — но, опять же, lint по дефолту не расмматривает потерянный break как ошибку — нужно вводить специальные метки в комметариях и тонкую настройку. А continous integration не везде есть.
                                                                                                          • 0
                                                                                                            Имхо все ваши проблемы решает самописная утилита-«защита от дурака». Потерянные брейки не рассматриваются как ошибки, естественно, потому, что пропуск брейка может быть сделан намеренно, упростив структуру программы. Так же, как и запятая в массиве может быть пропущена специально. Утилита-фильтр выведет строки, где вам надо отревьювить код более внимательно, и вы уже будете решать, надо оно там вам или нет.
                                                                                                            • 0
                                                                                                              Позволю себе самоцитироваться :)

                                                                                                              Тут, конечно, лучше настроенный lint — но, опять же, lint по дефолту не расмматривает потерянный break как ошибку — нужно вводить специальные метки в комметариях и тонкую настройку. А continous integration не везде есть.


                                                                                                              BTW, у меня нет проблем. Но я ищу способы улучшить, расширить и углубить. Исследовательская деятельность в целях изучения перспектив и потенциального улучшения процессов.
                                                                                • +3
                                                                                  Элегантный, хотя и лисповатый способ, спасибо. Хотя мне все чаще мечтается о клавиатурах с обратной связью, бьющие током за нажатие комбинаций <Ctr-C> + <Ctrl-V>.

                                                                                  Ну и, господа, пять страниц комментариев на тему «как правильно ставить скобочки»! Блестяще! :)
                                                                                  • +1
                                                                                    Слишком категорично: copy/paste скорее добро, чем зло.
                                                                                    Потому что он помогает избежать опечаток при переносе всяких URL, id, и т.п.
                                                                                    • +1
                                                                                      Нет-нет, не надо 10 киловольт и обугленных пальцев за любую копипасту. Я о другом.

                                                                                      Строго говоря, copy/paste вообще вне добра и зла. Это просто инструмент, который требует осторожности. То есть, прежде чем воткнуть скопированное, надо остановиться и немножко подумать. И почему-то мне кажется, что легкий неприятный разряд тока заставит многих помедлить, прежде чем воткнуть голые данные/кусок кода/копию настроек. А там, глядишь, мелькнет мысль «а может, сделать константу/функцию/секцию general в конфиге».
                                                                                      • 0
                                                                                        Главное, знать этому меру. А то придётся копипастить названия секций и ключей конфига.
                                                                                        Или сделать конфиг ключей конфигов :)
                                                                                        • +1
                                                                                          Слабый разряд злостные копипастеры перетерпят, а потом и привыкнут не замечать.
                                                                                          Надо подключать социальную составляющую: над головой копипастера загорается лампочка, воет сирена и страшный голос вещает: Имярек скопипастил 1420 знаков!
                                                                                      • +1
                                                                                        Атож. Зато я узнал что в Python и Ruby такой синтаксис чутое различается. И что отвечая на комментарий нужно не на синие кружочки слева смотреть, а на синюю стрелочку справа нажимать. Сплошной профит. Главное, чтобы подушка кармы была большой, у меня этот топик уже десятку съел :).
                                                                                      • НЛО прилетело и опубликовало эту надпись здесь
                                                                                        • 0
                                                                                          да ну прекратите.
                                                                                          это то же самое что и переменную перепутать. Тоже не просто дебажить и тоже надо код читать.
                                                                                          ни разу не имел проблем с пропавшими запятыми чесслово
                                                                                          • +2
                                                                                            Прочитал статью, порадовался, что у меня таких проблем нет.
                                                                                            Хотя не могу не поделиться своим наблюдением, там где «нужно» писать вертикально, то запятая теряется (у меня напремер в js). Но, когда я пишу списки на питоне, запятые не теряются никогда, потому что я их не пишу вертикально. Я правда за всю свою практику ни одного случая потери запятой в списке не помню, где бы это вылились в содержание топика.
                                                                                            • 0
                                                                                              Что люди только не придумают, лишь бы не пользоваться IDE
                                                                                              • +1
                                                                                                Странный вывод. Какая IDE и каким образом может помочь в описанной ситуации?
                                                                                                • –1
                                                                                                  Любая, которая интегрируется с упомянутым Вами pylint.
                                                                                                  т.е. просто практически любая.
                                                                                                  • +2
                                                                                                    Как мы определились выше, pylint не обнаруживает данную проблему. Чем поможет интеграция pylint в IDE?

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