Как решить эту проблему с наследованием в Python?

Мне нужно переопределить конфиг парсер так, чтобы при исключении программа configparser.DuplicateOptionError просто писала warning в логи, и продолжала работать. Я написал такой код:
import configparser


class CustomConfigParser(configparser.ConfigParser):
    def __init__(self, logger, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.logger = logger

    def _read(self, *args, **kwargs):
        try:
            super()._read(*args, **kwargs)
        except configparser.DuplicateOptionError as error:
            self.logger.warning(error)
            # тут теоритически должен быть continue, но поскольку мы используем super() - 
            # это не будет работать

Вот код метода в исходниках python
def _read(self, fp, fpname):
        """Parse a sectioned configuration file.

        Each section in a configuration file contains a header, indicated by
        a name in square brackets (`[]'), plus key/value options, indicated by
        `name' and `value' delimited with a specific substring (`=' or `:' by
        default).

        Values can span multiple lines, as long as they are indented deeper
        than the first line of the value. Depending on the parser's mode, blank
        lines may be treated as parts of multiline values or ignored.

        Configuration files may include comments, prefixed by specific
        characters (`#' and `;' by default). Comments may appear on their own
        in an otherwise empty line or may be entered in lines holding values or
        section names.
        """
        elements_added = set()
        cursect = None                        # None, or a dictionary
        sectname = None
        optname = None
        lineno = 0
        indent_level = 0
        e = None                              # None, or an exception
        for lineno, line in enumerate(fp, start=1):
            comment_start = sys.maxsize
            # strip inline comments
            inline_prefixes = {p: -1 for p in self._inline_comment_prefixes}
            while comment_start == sys.maxsize and inline_prefixes:
                next_prefixes = {}
                for prefix, index in inline_prefixes.items():
                    index = line.find(prefix, index+1)
                    if index == -1:
                        continue
                    next_prefixes[prefix] = index
                    if index == 0 or (index > 0 and line[index-1].isspace()):
                        comment_start = min(comment_start, index)
                inline_prefixes = next_prefixes
            # strip full line comments
            for prefix in self._comment_prefixes:
                if line.strip().startswith(prefix):
                    comment_start = 0
                    break
            if comment_start == sys.maxsize:
                comment_start = None
            value = line[:comment_start].strip()
            if not value:
                if self._empty_lines_in_values:
                    # add empty line to the value, but only if there was no
                    # comment on the line
                    if (comment_start is None and
                        cursect is not None and
                        optname and
                        cursect[optname] is not None):
                        cursect[optname].append('') # newlines added at join
                else:
                    # empty line marks end of value
                    indent_level = sys.maxsize
                continue
            # continuation line?
            first_nonspace = self.NONSPACECRE.search(line)
            cur_indent_level = first_nonspace.start() if first_nonspace else 0
            if (cursect is not None and optname and
                cur_indent_level > indent_level):
                cursect[optname].append(value)
            # a section header or option header?
            else:
                indent_level = cur_indent_level
                # is it a section header?
                mo = self.SECTCRE.match(value)
                if mo:
                    sectname = mo.group('header')
                    if sectname in self._sections:
                        if self._strict and sectname in elements_added:
                            raise DuplicateSectionError(sectname, fpname,
                                                        lineno)
                        cursect = self._sections[sectname]
                        elements_added.add(sectname)
                    elif sectname == self.default_section:
                        cursect = self._defaults
                    else:
                        cursect = self._dict()
                        self._sections[sectname] = cursect
                        self._proxies[sectname] = SectionProxy(self, sectname)
                        elements_added.add(sectname)
                    # So sections can't start with a continuation line
                    optname = None
                # no section header in the file?
                elif cursect is None:
                    raise MissingSectionHeaderError(fpname, lineno, line)
                # an option line?
                else:
                    mo = self._optcre.match(value)
                    if mo:
                        optname, vi, optval = mo.group('option', 'vi', 'value')
                        if not optname:
                            e = self._handle_error(e, fpname, lineno, line)
                        optname = self.optionxform(optname.rstrip())
                        if (self._strict and
                            (sectname, optname) in elements_added):
                            raise DuplicateOptionError(sectname, optname,
                                                       fpname, lineno)
                        elements_added.add((sectname, optname))
                        # This check is fine because the OPTCRE cannot
                        # match if it would set optval to None
                        if optval is not None:
                            optval = optval.strip()
                            cursect[optname] = [optval]
                        else:
                            # valueless option handling
                            cursect[optname] = None
                    else:
                        # a non-fatal parsing error occurred. set up the
                        # exception but keep going. the exception will be
                        # raised at the end of the file and will contain a
                        # list of all bogus lines
                        e = self._handle_error(e, fpname, lineno, line)
        self._join_multiline_values()
        # if any parsing errors occurred, raise an exception
        if e:
            raise e

Есть ли способ сделать что-то подобное не копируя весь метод c файла configparse.py?
  • Вопрос задан
  • 200 просмотров
Решения вопроса 2
trapwalker
@trapwalker Куратор тега Python
Программист, энтузиаст
Нифига. Никак тут не продолжить снаружи. Итальянец писал код - не иначе. Спагетти явно с сыром.
Но у вас есть возможность форкнуть и отрефакторить этот парсер, хотя лучше бы взяли просто другой хороший, пусть не из стандартной библиотеки.

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

О, ну и ещё одна идея на грани фола. Попробуйте _strict перекрыть свойством, а на геттер инициировать ворнинг. Правда вы так не узнаете на каком именно дубле проблема. А ещё, чтобы не путаться на случай нерелевантных обращений _strinct, можно включать режим ворнингов и выключать до и после перекрытого метода. Такой себе грязный хак основанный на побочных эффектах.

Можно пофиксить _strict параметр и ошибка не будет инициироваться, но вы останетесь без ворнинга.
Ответ написан
@Andy_U
Вы, конечно, посмотрели в документации на список аргументов конструктора класса и уже попробовали задать strict=False? Вас не устроил результат?

P.S. Прерывание точно не возникнет.
Ответ написан
Пригласить эксперта
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Похожие вопросы