Когда проект на python долгое время живет без правил по формату строк, то в один прекрасный момент оказывается, что 90% кода используют одинарные кавычки, а 10% - двойные.

Добавление flake8-quotes с соответствующими правилами перестало пускать новый код с двойными кавычками дальше пул-реквеста, но начало требовать ручной правки формата в уже существующем коде, чего хотелось бы избежать.

Первой мыслью было задействовать black, но предлагаемый им формат предполагает исключительно использование двойных кавычек. В 2018 в github black был запрос Single quotes option формата строк, обсуждение было жарким, но закончилось оно лишь введением опции --skip-string-normalization, позволявшей не трогать формат строк в проверяемом коде.

Самым простым в данной ситуации было просто согласиться с black, но команда и бóльшая часть кода были против. Пришлось искать другое решение, которое оказалось в модификации исходного кода black.

Сразу скажу, что все правила реализовать не получилось, так как в flake8-quotes можно задать разные правила для docstring и остальных многострочных строк, а логика black их, насколько удалось разобраться, не различает.

Тем не менее, с двойными кавычками в простых строках справиться удалось,

и вот что для этого потребовалось:

  • были скачаны исходники black 22.1.0 (это первая не-бета версия black и наш CI в данный момент использует эту версию в режиме проверки кода).

  • установлены необходимые ему библиотеки

Для Python 3.8:
click             8.1.3
colorama          0.4.4
mypy-extensions   0.4.3
pathspec          0.9.0
pip               21.3.1
platformdirs      2.5.2
tomli             2.0.1
typing_extensions 4.2.0
wheel             0.37.1

  • добавлен модуль src/black/_black_version.py c содержимым version = '22.1.0'

Основные правки оказались сконцентрированы в методе normalize_string_quotes модуля src/black/strings.py:

Условие на входе метода

    if value[:3] == '"""':
        return s

    elif value[:3] == "'''":
        orig_quote = "'''"
        new_quote = '"""'
    elif value[0] == '"':
        orig_quote = '"'
        new_quote = "'"
    else:
        orig_quote = "'"
        new_quote = '"'

заменил на

if value[:3] == '"""':
    return s

elif value[:3] == "'''":
    return s  # оставляем как есть и строки с ''', и строки с """
elif value[0] == '"':
    orig_quote = '"'
    new_quote = "'"
else:
    orig_quote = "'"
    new_quote = '"'

а условие на выходе метода

    if new_escape_count == orig_escape_count and orig_quote == '"':
        return s  # Prefer double quotes

заменил на

    if new_escape_count == orig_escape_count and orig_quote == "'":
        return s  # Prefer single quotes

Через несколько минут работы модифицированного black проблема двойных кавычек в нашем проекте была решена (хорошо, остался еще этап ручного исправления """ в многострочных строках на ''', но он тоже не занял много времени).

Из приятного. В коде нашлось несколько строк вида u'\xa0' - видимо скопированных из советов для python 2 - и исправилось (удалило u в начале). Также исправились сами экранированные строки "\"" в '"'

Из неожиданного. Строки fr'\abc' заменило на rf'\abc'. Таких правок при необходимости легко было избежав закомментировав вызов метода normalize_string_prefix.

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

Что же касается opensource проектов, тут, я полагаю, все уже давно решено и black с его двойными кавычками победил.

Комментарии (34)


  1. Tanner
    19.06.2022 04:09
    +1

    Меня тоже двойные кавычки подбешивают в некоторых проектах. Мне кажется, лучше всего базировать стайл-гайд на том, как работает repr() для строк: одиночные кавычки за исключением тех случаев, когда двойные кавычки помогают избежать лишнего ескейпинга. Но уж лучше кривой стайл-гайд, чем никакого.


  1. anonymous
    00.00.0000 00:00


  1. SergeiMinaev
    19.06.2022 06:22
    -5

    Двойные кавычки в питоне, наверное, используют те, кто несколько раз нажимает пробел вместо одного таба :) Вспомнилось - https://www.youtube.com/watch?v=AXLoRpKnK8U


    1. FibYar
      19.06.2022 09:40
      +15

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


  1. nin-jin
    19.06.2022 09:03
    +23

    Ерундой вы какой-то занимаетесь вместо работы. Что, дедлайн далеко, можно кавычки поперекрашивать?


  1. rrrad
    19.06.2022 09:27
    +4

    В 2018 в github black был запрос Single quotes option формата строк, обсуждение было жарким, но закончилось оно лишь введением опции --skip-string-normalization, позволявшей не трогать формат строк в проверяемом коде.

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


  1. Xobotun
    19.06.2022 09:50
    +6

    Я тут немного ради похоливарить и любопытство потешить. В джаве строка – всегда двойные кавычки, а единичный символьный литерал – одинарные. В груви двойные кавычки делают интерполируюмую GString, а одинарные – просто строку. В баше, кажется, тоже, внутри двойных кавычек $ eval'уируется.

    Питоном же я почти не пользовался, да и то 2.7 веткой, когда она ещё жива была. Посему вопрос – а чем плохи двойные кавычки?


    1. baldr
      19.06.2022 10:10
      +3

      Пишу на Питоне больше 10 лет, но тоже удивился и зашел узнать ответ на этот вопрос.

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

      Судя по всему, это просто режет глаз кому-то сильно ортодоксальному. Вопрос похож на спор - какой размер табуляции использовать - 3 или 4 символа.


    1. 57an Автор
      19.06.2022 10:26
      +2

      Вопрос не в том, что двойные кавычки чем-то плохи. Вопрос в том, как ввести правила по кавычкам на легаси-проекте, применив их сразу ко всему проекту и сохранив статистические предпочтения. Если бы такое же решил сделать в JavaScript, там eslint с опцией --fix сделал бы все автоматом, какой бы формат кавычек я бы не задал в правилах. Для Python такое решение - black - есть только для двойных кавычек.

      Почему вообще нужны правила по кавычкам? Потому что если их не заводить, то по моему опыту периодически в пул-реквестах команды появляются случайные правки кавычек в старом коде. Ребята просто берут и правят все одиночные на двойные или наоборот в каком-то файле или куске файла, без видимых причин, потому что им показалось что так лучше. И с их точки зрения они вообще молодцы, что прибрались в коде, применили правило бой-скаута на деле. Чтобы не занимались этим вместо полезной работы ввожу правила и пусть за этим следит линтер.


    1. BubaVV
      19.06.2022 11:34
      -1

      Их надо с шифтом вводить


    1. netch80
      19.06.2022 11:59
      +1

      Я пока что увидел для себя две причины выбирать один из вариантов:
      1. Когда набираешь if __name__ == "__main__", с двойными кавычками меньше нажатий/отжатий Shift :)
      2. В определённых областях применения — внутри целевых строк одиночные или двойные встречаются сильно чаще. Например, если вы рисуете подстановку строк-литералов в SQL, им требуются одинарные => вокруг надо делать двойные, а если имён объектов (таблиц, колонок) — двойные. (Да, я знаю, что по идеальной норме это всё давно сделано в некотором middleware. Это только для примера специфичной области.)
      С другой стороны, тут скорее хочется чего-то в стиле C++ `R«abc(x»y«z»)abc"`, чтобы вообще иметь один универсальный независимый от контента формат квотинга. Ну или хотя бы `qq(x«y»z")`, как в Perl.
      3. Редакторы, заточенные под текст, а не код, конвертят (сразу или при генерации выхода) ​"​x​"​ в “x”, причём плюя на локаль (для русского должно было быть „x“). С одинарными кавычками такого не происходит. Иногда это становится важным (сайты статей типа хабра, учебные материалы).


      1. tdemin
        19.06.2022 20:59

        Когда набираешь if __name__ == "__main__", с двойными кавычками меньше нажатий/отжатий Shift :)

        В том же VSCode на это вместе с расширением Python идет сниппет, позволяющий main<Tab>.

        R«abc(x»y«z»)abc"

        Ох уж этот новый редактор хабра.


    1. economist75
      20.06.2022 09:58

      Очевидно же, что двойные кавычки плохи тем, что они распространены в других языках программирования (Python - "клей", часто использует инжекты другого кода). Ну и в современном прагматичном русском двойные кавычки (не елочки, они вредны во всех отношениях) - в названия компаний ООО "Ромашка", в литературе итд. А значит их труднее (чем в одинарных), внедрить и сохранить в строке python без экранирования, а значит правильнее всегда использовать одинарные кавычки (меньше печатать), сравните:

      print('ООО "Ромашка"','далее','"исполнитель"')

      print("ООО "\"Ромашка\"','далее','\"исполнитель\"') # на +5 больше нажатий клавиш (+10%)

      Два варианта кавычек - еще одна киллерфича Питон, за какие его и любят. 10% роста производительности труда/печатания на дороге не валяются. Исключение - обязательное использование тройных двойных """ по PEP8 для docstring функций, это для автодокументирования, будущего парсинга кода для книгоиздателей итп. Ну и просто красиво. Их и набирать не надо - приличная IDE их после def() напечатает сама. Тройные одинарные тоже работают, но читаются хуже.


      1. baldr
        20.06.2022 10:05

        query = "select * from table where status = 'active'"

        Вот вам примеры когда одинарные кавычки внутри текста лучше заключать во внешние двойные.

        print("Please enter user's first name:")


  1. amarao
    19.06.2022 10:37
    +5

    Меня аргументы black'а убедили.

    Сравните: '' + '' и " + "

    Одно - пустая строка, второе - плюсик с пробелами.
    Игры со шрифтами позволят увидеть разницу '' + '' и " + "

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


    1. baldr
      19.06.2022 10:42
      +6

      Вы хотите сказать что бывают такие, кто пишет код не с моноширинным шрифтом? Вот такие на Python я даже не представляю что могут выжить без повреждения мозга.


      1. amarao
        19.06.2022 10:54
        +4

        Даже в моноширинном шрифте они всё равно вызывают неоднозначность, если находятся в середине сложного выражения.

        Аргумент есть. А вот у сторонников одинарных кавычек аргументов кроме "мне так нравится" нет.

        До аргументации black'а у меня не было оформленного мнения (кроме того, что любого человека из C/Rust бэкграунда корёжит от того, что путают &str и char), а тут появилось. Более того, оно появилось как готовое решение, которому можно просто подчиниться и забыть навсегда этот вопрос.


        1. netch80
          19.06.2022 12:05

          > А вот у сторонников одинарных кавычек аргументов кроме «мне так нравится» нет.

          (повторюсь из соседнего комментария)
          Редакторы, заточенные под текст, а не код, конвертят (сразу или при генерации выхода) ​"​x​"​ в “x”, причём плюя на локаль (для русского должно было быть „x“). С одинарными кавычками такого не происходит. Иногда это становится важным (сайты статей типа хабра, учебные материалы). С такими проще всё перевести на одинарные.


          1. amarao
            19.06.2022 12:20

            Корёжит оба типа.


            1. netch80
              19.06.2022 13:11

              Кто этот злодей? Имя, сестра!


              1. amarao
                19.06.2022 13:13

                https://medium.com/

                Самый типографичный сайт для текстов.


                1. netch80
                  19.06.2022 13:18
                  -1

                  > medium.com

                  Мировое сборище жлобства за пейволлом? Не аргумент.


                  1. amarao
                    19.06.2022 13:49

                    У меня там блог без всякого жлобства, да и читаю я там много людей так же, как я и читал их до появления paywall'а.

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


                    1. netch80
                      19.06.2022 13:59

                      >… Вообще, при чём тут модель монетизации?

                      При том, что его поведение таки нетипично. Ну да, «не происходит» надо было уточнить стандартным «в ~95% случаев», тут уж извините.

                      > У меня там блог без всякого жлобства

                      Ну так авторов там стараются холить и лелеять, бесспорно.


                      1. amarao
                        19.06.2022 14:41

                        Ну я сходу показал существование редакторов, которые корраптят оба вида.

                        Проверил в гугльдоках:

                        Интуиция мне говорит, что office 365 сделает то же самое.

                        Так что я отвергаю аргумент о том, что одинарные кавычки лучше переживают копипаст через типограферы.


          1. withkittens
            19.06.2022 16:20
            +1

            для русского должно было быть „x“

            Для русского должно быть «х».


    1. rrrad
      19.06.2022 11:02
      +3

      Даже если абстрагироваться от того, что пример высосан из пальца, само по себе это бред, не знаю ни одного ЯП, где было бы нормальным использование немоноширинного шрифта. А с Питоном - тут это особенно важно. При публикации кода на веб-ресурсах (в т.ч. на Хабре) принято тоже код моноширинным шрифтом оформлять.


      1. amarao
        19.06.2022 11:07
        +1

        Безусловно, но аргумент есть. Есть ли какой-то контр-аргумент в другую сторону?


        1. baldr
          19.06.2022 11:20

          Конечно же холивары подобные этим уже давно есть в интернете.

          Раз, два, ну и гугл.


          1. amarao
            19.06.2022 11:33

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

            А у black есть однозначный аргумент. Слабый, но аргумент.


  1. alTus
    19.06.2022 13:32
    +1

    В том треде репозитория black накидали очень много хороших аргументов, почему будет лучше, когда стиль кавычек можно выбрать самому. Хорошо, что мейнтейнер хотя бы добавил skip-string-normalization, а вообще мне показалось, что он отклонил запрос исходя из собственных субъективных предпочтений.

    Когда мы у себя внедряли black, я тоже рассматривал вариант с форком (но это сразу показалось сложно и лениво), поэтому в итоге пришли к варианту c pre-commit: т.е. в начале в .pre-commit-config.yaml указан black, потом isort, а потом double-quote-string-fixer (встроенный в pre-commit хук). Works like a charm.


  1. zloddey
    19.06.2022 15:28

    Использую black в своих питонячьих пет-проектах, форматирование включено в стандартный флоу. Один вызов make сначала приведёт стиль в норму, а потом запустит тесты. В общем-то, ради тестов я его обычно и запускаю, а форматирование становится приятным бонусом. Получается так: пишу код как хочу - с одинарными кавычками, длинными строками и т.п. - но на выходе автоматически получается "правильный" код. Ещё до попадания в коммит! Иногда система чуток сбоит (делаю коммит без проверки), но это не особо напрягает.

    Все автоматические утилиты имеют свою "цену использования". Чем дальше они от непосредственного момента написания кода, чем дольше работают, и чем больше ставят препонов, тем эта цена выше. Она выражается как в времени разработчика, которое уходит на переделку, так и в "затрачиваемых нервах". С этой точки зрения, black - практически идеальный инструмент, если использовать его правильно. А вот flake я раньше использовал, но со временем выкинул - потому что он постоянно душнил к месту и не к месту, и при этом не предоставлял инструментов для автоматической чистки кода. Другими словами, цена использования оказалась для меня выше, чем приносимый профит.

    Массовое форматирование кода - неприятная штука, конечно. Но оно делается один раз на всю кодовую базу, после чего уже можно жить в новом, гораздо более приятном окружении. И не тратить кучу сил на впиливание и дальнейшую поддержку костылей по типу описанных в статье (уж извините).


  1. didyk_ivan
    20.06.2022 09:55
    +3

    Вопрос из чистого любопытства (возможно бестолковый) - а чем смесь разных типов кавычек плоха?


  1. Rustified
    20.06.2022 09:56
    +1

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