ConfigParser и Unicode

    В Python есть очень удобный модуль для сохранения и чтения ini-подобных конфигурационных файлов, который называется ConfigParser.

    У меня при его использовании возникла проблема, связанная с сохранением в файл Unicode-строк. В некоторых трудноуловимых случаях (например, у меня это проявилось при работе приложения под Windows XP) при чтении или записи таких параметров выскакивает ошибка конвертации строк.

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

    Хочу предложить свое решение для тех, кто использует Python 2.X — оно довольно простое и помогает решить эту проблему.



    Во-первых, нужно унаследовать класс RawConfigParser, переопределив метод write() — а именно — заменив все вызовы str() на вызовы unicode():

    Copy Source | Copy HTML
    1. class UnicodeConfigParser(ConfigParser.RawConfigParser):
    2.  
    3.     def __init__(self, *args, **kwargs):
    4.         ConfigParser.RawConfigParser.__init__(self, *args, **kwargs)
    5.  
    6.     def write(self, fp):
    7.         """Fixed for Unicode output"""
    8.         if self._defaults:
    9.             fp.write("[%s]\n" % DEFAULTSECT)
    10.             for (key, value) in self._defaults.items():
    11.                 fp.write("%s = %s\n" % (key, unicode(value).replace('\n', '\n\t')))
    12.             fp.write("\n")
    13.         for section in self._sections:
    14.             fp.write("[%s]\n" % section)
    15.             for (key, value) in self._sections[section].items():
    16.                 if key != "__name__":
    17.                     fp.write("%s = %s\n" %
    18.                              (key, unicode(value).replace('\n','\n\t')))
    19.             fp.write("\n")
    20.  
    21.     # This function is needed to override default lower-case conversion
    22.     # of the parameter's names. They will be saved 'as is'.
    23.     def optionxform(self, strOut):
    24.         return strOut
    25.  


    Во-вторых, запись и чтение конфигурационного файла нужно делать с оберткой для open() из модуля codecs, которой нужно указать utf-8 в качестве кодировки. В случае загрузки это можно сделать, если использовать для чтения не read(), а readfp():

    Copy Source | Copy HTML
    1. import codecs
    2.  
    3. # Saving
    4.  
    5. confFile = codecs.open('myConfig.ini', 'w', 'utf-8')
    6. config = UnicodeConfigParser()
    7. # ...
    8. config.write(confFile)
    9. confFile.close()
    10.  
    11. # Loading
    12.  
    13. config = UnicodeConfigParser()
    14. config.readfp(codecs.open('myConfig.ini', "r", "utf-8"))


    Надеюсь, кому-нибудь пригодится. Если у вас есть более красивое и удачное решение, буду рад его услышать.
    Ads
    AdBlock has stolen the banner, but banners are not teeth — they will be back

    More

    Comments 14

      +5
      На хабре уже упоминали: отличное решение — это просто использовать ConfigObj. Он и Юникод умеет, и валидацию, и значения по умолчанию, и конверсию типов, и вложенные секции.
        0
        а с тем, который просто config не сравнивали? я, правда, пока его использовал только в мелких «наколенных» скриптах, но пока нравится… нашел его, кажись, тоже на хабре…
          0
          ну судя по документации: вроде приятный парсер, но там свой синтаксис конфига. Как по мне, то он местами даже лучше чем ini-style, но непривычный.

          cross-references — прикольно;
          Кучи скобок — смотрятся немного стрёмно в конфиге, но напоминают синтаксис питона. Даже не знаю хорошо это или плохо
          $` — напоминает sh
          evaluating values — наверное удобно иногда, но редко. Лучше и безопаснее в коде как-то обрабатывать.

          Чего не заметил (в сравнении с ConfigObj):
          нет валидации конфигов и возможности создать темплейт для приведения к питоновским типам.
        0
        спасибо за совет, попробую посмотреть :)
          0
          Небольшое лирическое отступление.
          В сообществе Python-программистов следует всеми правдами и неправдами продавливать YAML как основной формат конфигураций. А также агитировать за игнорирование PasteDeploy и PasteScript до тех пор, пока они не избавятся от «беспредела» в своём коде.
            +1
            А существуют обоснованные аргументы в пользу YAML? Дайте ссылку, если вас не затруднит.
              0
              какие аргументы для вас будут обоснованными?
                0
                Ну возможно запись в блоге. Или обсуждение на каком-то форуме.
                А вы как пришли к такому мнению?
                  0
                  Например, благодаря PEP 391 в Python 2.7 появился способ конфигурирования стандартного пакета логирования через словари. Примеры кофигурации представлены в YAML-нотации, а сам подход объявлен предпочтительным перед вариантом с ConfigParser-ом.

                  Про Paste. Я занимаюсь разработкой под Pylons, в котором PasteDeploy и PasteScript идут в качестве зависимостей. До того момента, как проект Pylons было решено забросить и начать разработку Pyramid, разработчики объявляли о намерении заменить Paste на самописный пакет Marco, в котором присутствовала возможность конфигурирования посредством YAML. Но с того момента проект успешно заглох. Переписка с разработчиками показала, что они не в большом восторге от Paste (а тому есть очень веские причины), но так как он «просто работает» и «это не высокоприоритетная задача», дорабатывать Marco в ближайшее время никто не собирается.
                  Когда я переводил свой проект на YAML-конфигурации без использования Paste, мне пришлось лезть и разбираться в его исходниках. Сказать, что Paste ужасен — ничего не сказать. Он изначально предлагает ущербный формат, без права выбора, и поверх этого строит свою неструктурированную кодовую базу, состоящую из хуков типа paste.deploy.converters и вложенных кусков посторонних пакетов (вроде CherryPyWSGIServer из пакета CherryPy).
                  В итоге, процесс конфигурирования более-менее сложного проекта с помощью Paste превращается в уродский набор инструкций на уровне python-кода вашего приложения.

                  Лично мне кажется, что INI прижился в среде Python-разработчиков только благодаря тому, что он очень давно существует в стандартной библиотеке (точно присутствовал уже в 1.6) и ему до недавнего времени не было абсолютно никаких альтернатив — не было других «парсеров из коробки». Но ситуация меняется. Станадртный парсер JSON, основанный на коде simplejson, появился только в версии 2.6. Дойдёт время и до включения PyYAML в стандартную поставку.
                  0
                  Я немного погуглил на эту тему: yaml парсер самый медленный во всех сравнениях. Плюс самый малораспространённый.
                    0
                    Вы же конфигурацию один раз во время инициализации приложения читатете. Зачем вам измерять преимущества формата по принципу скорости парсинга?
              +2
              Параметр dict_type появился только в 2.6, поэтому инициализацию лучше заменить на:
              def __init__(self, *args, **kwargs):
               ConfigParser.RawConfigParser.__init__(self, *args, **kwargs)
                0
                спасибо, поправил :)
                +3
                Ну не знаю насчет решения; чтоб придумать более красивое и удачное решение, нужно какое-то более детальное проблемы (трейс?), а тут проблемы конкретной не обозначено, только «что-то иногда не работает» и какой-то кусок кода, который вроде бы как-то проблему решает.

                вот это:
                unicode(value)
                

                не есть хорошо, т.к. преобразование будет проходить с непонятно какой кодировкой (ascii по дефолту?).

                С кодировками-то все просто ведь, главное на каждом этапе понимать, с каким типом данных мы имеем дело (например, строка в utf8 или юникодная строка). Тут одна тонкость только есть — при конкатенации юникодных и неюникодных строк происходит неявное преобразование неюникодной строки в юникод, при этом используется дефолтная кодировка, что приводит к UnicodeEncodeError (особенно на win), т.к. эта кодировка часто ascii. Т.е. если сверху в файле написано «coding: utf8», и дальше объявлена константа на русском без u спереди, то вполне вероятно, что под nix все будет работать, а под win — неожиданно упадет. Выход — не складывать строки разных типов (str и unicode), а преобразовывать все в юникод пораньше.

                Only users with full accounts can post comments. Log in, please.