2014-10-14 2 views
8

Парсер, который я создал, читает записанные шахматные игры из файла. API используется так:Что такое Pythonic способ сообщить о нефатальных ошибках в парсере?

import chess.pgn 

pgn_file = open("games.pgn") 

first_game = chess.pgn.read_game(pgn_file) 
second_game = chess.pgn.read_game(pgn_file) 
# ... 

Иногда нелегальные ходы (или другие проблемы) встречаются. Что такое хороший питоновский способ справиться с ними?

  • Исключение исключений, как только возникнет ошибка. Однако это делает каждую проблему фатальной, при этом выполнение останавливается. Часто все еще есть полезные данные, которые были проанализированы и могут быть возвращены. Кроме того, вы не можете просто продолжить разбор следующего набора данных, потому что мы все еще находимся в середине некоторых получитаемых данных.

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

  • Ввести дополнительный аргумент вроде этого:

    game = chess.pgn.read_game(pgn_file, parser_info) 
    if parser_info.error: 
        # This appears to be quite verbose. 
        # Now you can at least make the best of the sucessfully parsed parts. 
        # ... 
    

некоторые из этих или других методов, используемых в дикой природе?

+2

Существует множество различных опций - используйте 'logging' (например, просто предупреждайте, что игра такая-то не может быть разобрана), есть дополнительный аргумент' suppress_errors', ... Вы хотите, читать игру, которую нужно вернуть? Просто пропустил? Должен ли это вариант? Это слишком широко, чтобы на самом деле правильно ответить - вам нужно решить, что вы хотите, чтобы ваш API выполнял (тогда документируйте его!) – jonrsharpe

+0

Третий вариант не нужен; вы можете определить настраиваемое исключение, которое включает в себя любую информацию, содержащуюся в 'parser_info'. – chepner

+1

Хороший пост в блоге. https://www.jeffknupp.com/blog/2013/02/06/write-cleaner-python-use-exceptions/ вопрос действительно вопрос стиля LBYL против EAFP. Python предпочитает EAFP и более лаконично поднимать исключение или проходить. Я думаю, что компиляция списка исключений для пользователя перед созданием игры - лучший выбор. Возможно, используйте while info.error: чтобы отправить запрос обратно пользователю. Это не смертельно, просто проверка. –

ответ

6

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

Учитывая возможность как фатальных ошибок (файл поврежден), так и предупреждений (был выполнен незаконный ход, но последующие ходы показывают согласованность с этим движением (другими словами, ошибка пользователя и никто не поймал его в то время)) I рекомендуют комбинацию первого и второго вариантов:

  • возбудит исключение при продолжении синтаксического анализа не вариант
  • собирать любые ошибки/предупреждения, которые не не исключают дальнейшего разбора до конца

Если вы не сталкиваетесь тер фатальную ошибку, то вы можете вернуть игру, плюс любые предупреждения/не фатальные ошибки, в конце:

return game, warnings, errors 

Но что делать, если вы попали фатальную ошибку?

Нет проблем: создать специальное исключение, к которому можно прикрепить полезную часть игры и любые другие предупреждения/некритические ошибки в:

raise ParsingError(
    'error explanation here', 
    game=game, 
    warnings=warnings, 
    errors=errors, 
    ) 

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

Обычай ошибка может быть:

class ParsingError(Exception): 
    def __init__(self, msg, game, warnings, errors): 
     super().__init__(msg) 
     self.game = game 
     self.warnings = warnings 
     self.errors = errors 

и в использовании:

try: 
    first_game, warnings, errors = chess.pgn.read_game(pgn_file) 
except chess.pgn.ParsingError as err: 
    first_game = err.game 
    warnings = err.warnings 
    errors = err.errors 
    # whatever else you want to do to handle the exception 

Это подобно тому, как subprocess модуль обрабатывает ошибки.

Для возможности извлекать и анализировать последующие игры после игры фатальной ошибки, я хотел бы предложить изменения в вашем API:

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

Таким образом, если у вас есть файл с пятью играми и игра две ди es, вы все равно можете попытаться проанализировать игры 3, 4 и 5.

+0

Я согласен с этим ответом: ваш парсер должен всегда сообщать о его выполнении, но должен возбуждать исключение, если он не может выполнять свою работу. Код верхнего уровня, который запрашивал итерацию по файлу, должен знать о возможных исключениях и должен реагировать соответствующим образом. –

+0

Принимая это сейчас. Лучший ответ может отличаться от случая использования в случае использования, указывая, что мой первоначальный вопрос является довольно неоднозначным или открытым. Также полезны протоколирование, предупреждения и ответ генератора. – Niklas

3

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


warnings module это именно то, что вы хотите.

Что превратило меня от него в первый том, что каждый пример предупреждение используется в документации выглядит these:

Traceback (most recent call last): 
    File "warnings_warn_raise.py", line 15, in <module> 
    warnings.warn('This is a warning message') 
UserWarning: This is a warning message 

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

Вот решение, что:

import warnings 
class AmbiguousStatementWarning(Warning): 
    pass 

def x(): 
    warnings.warn("unable to parse statement syntax", 
        AmbiguousStatementWarning, stacklevel=3) 
    print("after warning") 

def x_caller(): 
    x() 

x_caller() 

, который дает:

$ python3 warntest.py 
warntest.py:12: AmbiguousStatementWarning: unable to parse statement syntax 
    x_caller() 
after warning 
+1

Спасибо, я уже забыл этот вопрос. Прежде чем я соглашусь, давайте посмотрим, есть ли другие ответы, привлеченные щедростью. – Niklas

+0

@ АндрейБеньковский Niklas, скорее всего, будет ждать по крайней мере до тех пор, пока моя щедрость не закончится, не стоит беспокоиться. – cat

+0

'warnings' больше об информировании пользователя, а не о вызывающем коде. Если ваш модуль зависел от правильного выполнения 'x_caller()', он не знал бы, что возникла проблема. –

7

Наиболее Pythonic путь является logging модуль. Это было упомянуто в комментариях, но, к сожалению, без особого упорства. Есть много причин, почему предпочтительнее warnings:

  1. модуль предупреждения предназначен сообщать предупреждения о потенциальных код вопросов, а не плохие пользовательские данные.
  2. Первая причина на самом деле достаточно. :-)
  3. Модуль регистрации обеспечивает настраиваемую серьезность сообщения: не только предупреждения, но и сообщения отладочных сообщений до критических ошибок.
  4. Вы можете полностью управлять выходом модуля регистрации.Сообщения могут быть отфильтрованы по источнику, содержимому и строгости, отформатированы любым способом, отправлены в разные выходные цели (консоль, трубы, файлы, память и т. Д.) ...
  5. Модуль протоколирования разделяет фактическую отчетность об ошибках/предупреждениях/сообщениях и вывод: ваш код может генерировать сообщения соответствующего типа и не должен беспокоиться о том, как они представлены конечному пользователю.
  6. Модуль регистрации является стандартом де-факто для кода Python. Все используют его везде. Поэтому, если ваш код использует его, объединение его с сторонним кодом (вероятно, с помощью ведения журнала) будет легким. Ну, может быть, что-то более сильное, чем ветер, но определенно не ураган категории 5. :-)

Основной вариант использования модуля протоколирования будет выглядеть так:

import logging 
logger = logging.getLogger(__name__) # module-level logger 

# (tons of code) 
logger.warning('illegal move: %s in file %s', move, file_name) 
# (more tons of code) 

Это будет печатать сообщения, как:

WARNING:chess_parser:illegal move: a2-b7 in file parties.pgn 

(предполагается, что ваш модуль называется chess_parser.py)

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

logging.basicConfig(filename = 'parser.log', format = '%(name)s [%(levelname)s] %(message)s') 

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

chess_parser [WARNING] illegal move: a2-b7 in file parties.pgn 

Или вы можете подавить предупреждения, если вы хотите:

logging.basicConfig(level = logging.ERROR) 

И ваш модуль W arnings будут полностью игнорироваться, тогда как любые сообщения ERROR или более высокого уровня из вашего модуля будут обрабатываться.

+0

Это хороший ответ, однако он слишком * слишком * вовлечен в то, что мне нужно, и лично я, вероятно, буду придерживаться «предупреждений». Кроме того, вы указываете, что * «модуль предупреждений предназначен для ** кода пользователя **, а не ** плохих данных **» *, с которым я согласен, но поскольку я разрабатываю интерпретатор для языка, а не только для сборщика данных , Я не уверен, следует ли рассматривать код для интерпретации как * code * или как * data *. – cat

+1

Кроме того, я изучаю его прямо сейчас, но я * думаю * Я бы предпочел, чтобы предупреждения выглядели как исключения, похожие на сообщения журнала. 'warnings' дает стиль traceback-y по умолчанию, тогда как' logging' предназначен для сообщений с одним слоем. – cat

+1

@cat Вовлечение для базового сценария практически не изменилось. Модуль предупреждений обычно используется для создания таких сообщений, как «вы используете устаревшую библиотеку HTTPS, которая может быть проблемой безопасности, пожалуйста, обновите», что является совершенно другой чашкой чая из этой ситуации. И форматирование полностью настраивается с помощью 'logging'. Но, конечно, есть много способов скинуть кошку ... никакой каламбур. :-) – Lav

0

Без библиотек трудно сделать это чисто, но все же возможно.

Существуют различные способы обращения с этим, в зависимости от ситуации.

Метод 1:

Поместите все содержимое в то время как петли внутри следующего:

while 1: 
    try: 
     #codecodecode 
    except Exception as detail: 
     print detail 

Способ 2:

То же, что метод 1, за исключением того, имея несколько попробовать/за исключением того, рюшечки, так это то, не пропускает слишком много кода &, вы знаете точное местоположение ошибки.

Извините, в спешке, надеюсь, это поможет!

+0

-1 Если бы я мог: недостаточно чистить, на мой взгляд. Кроме того, мы не хотим * исключений *, мы хотим * предупреждений *, но даже если мы продвигаем * предупреждения * к * исключениям *, 'warnings' и' logging' лучше, чем просто обработка исключений. – cat

+0

@cat: OP ничего не говорит о желании «предупреждений» и не хочет исключений, а также не имеет текста благодарности. –

3

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

def process_items(items) 
    for item in items: 
     try: 
      #process item 
      yield processed_item, None 
     except StandardError, err: 
      yield None, (SOME_ERROR_CODE, str(err), item) 


for processed, err in process_items(items): 
    if err: 
     # process and log err, collect failed items, etc. 
     continue 
    # further process processed 

Более общий подход заключается в практическом использовании шаблонов проектирования. Упрощенная версия Observer (при регистрации обратных вызовов для определенных ошибок) или вид Visitor (где у посетителя есть методы для обработки конкретных ошибок, см. Раздел SAX для анализа) может быть ясным и понятным решением.

+0

Хммм, мне это нравится. – cat

+0

@cat я обновил свой ответ и добавил пару мыслей. – newtover

Смежные вопросы