Чисто Python способ обернуть отдельные операторы в try, кроме блока - PullRequest
8 голосов
/ 01 сентября 2011

В настоящее время я делаю некоторую автоматизацию Python для Excel с com. Он полностью функционален и делает то, что я хочу, но я обнаружил нечто удивительное. Иногда некоторые из команд Excel, которые я использую, завершаются с ошибкой без видимой причины. В других случаях они будут работать.

В VB-эквивалентном коде того, что я делаю, эта проблема, по-видимому, считается нормальной и оштукатурена с помощью оператора On Error Resume Next. Конечно, в Python нет такого утверждения.

Я не могу свернуть весь набор в петлю try except, потому что он может "потерпеть неудачу" на полпути и не завершиться должным образом. Итак, каков будет питонный способ заключить несколько независимых операторов в попытку, кроме блока? В частности, что-то чище, чем:

try:
   statement
except:
   pass
try:
   statement
except:
   pass

Соответствующим кодом является excel.Selection.Borders бит.

def addGridlines(self, infile, outfile):
    """convert csv to excel, and add gridlines"""
    # set constants for excel
    xlDiagonalDown = 5
    xlDiagonalUp = 6
    xlNone = -4142
    xlContinuous = 1
    xlThin = 2
    xlAutomatic = -4105
    xlEdgeLeft = 7
    xlEdgeTop = 8
    xlEdgeBottom = 9
    xlEdgeRight = 10
    xlInsideVertical = 11
    xlInsideHorizontal = 12
            # open file
    excel = win32com.client.Dispatch('Excel.Application')
    workbook = excel.Workbooks.Open(infile)
    worksheet = workbook.Worksheets(1)

    # select all cells
    worksheet.Range("A1").CurrentRegion.Select()
    # add gridlines, sometimes some of these fail, so we have to wrap each in a try catch block
    excel.Selection.Borders(xlDiagonalDown).LineStyle = xlNone
    excel.Selection.Borders(xlDiagonalUp).LineStyle = xlNone
    excel.Selection.Borders(xlDiagonalUp).LineStyle = xlNone
    excel.Selection.Borders(xlEdgeLeft).LineStyle = xlContinuous
    excel.Selection.Borders(xlEdgeLeft).Weight = xlThin
    excel.Selection.Borders(xlEdgeLeft).ColorIndex = xlAutomatic
    excel.Selection.Borders(xlEdgeTop).LineStyle = xlContinuous
    excel.Selection.Borders(xlEdgeTop).Weight = xlThin
    excel.Selection.Borders(xlEdgeTop).ColorIndex = xlAutomatic
    excel.Selection.Borders(xlEdgeBottom).LineStyle = xlContinuous
    excel.Selection.Borders(xlEdgeBottom).Weight = xlThin
    excel.Selection.Borders(xlEdgeBottom).ColorIndex = xlAutomatic
    excel.Selection.Borders(xlEdgeRight).LineStyle = xlContinuous
    excel.Selection.Borders(xlEdgeRight).Weight = xlThin
    excel.Selection.Borders(xlEdgeRight).ColorIndex = xlAutomatic
    excel.Selection.Borders(xlInsideVertical).LineStyle = xlContinuous
    excel.Selection.Borders(xlInsideVertical).Weight = xlThin
    excel.Selection.Borders(xlInsideVertical).ColorIndex = xlAutomatic
    excel.Selection.Borders(xlInsideHorizontal).LineStyle = xlContinuous
    excel.Selection.Borders(xlInsideHorizontal).Weight = xlThin
    excel.Selection.Borders(xlInsideHorizontal).ColorIndex = xlAutomatic
    # refit data into columns
    excel.Cells.Select()
    excel.Cells.EntireColumn.AutoFit()
    # save new file in excel format
    workbook.SaveAs(outfile, FileFormat=1)
    workbook.Close(False)
    excel.Quit()
    del excel

Обновление

Возможно, требуется немного пояснений к биту ошибки. Два идентичных запуска на моей тестовой машине с одинаковым кодом в одном и том же файле дают одинаковый результат. Один прогон создает исключения для каждой строки xlInsideVertical. Другие бросают исключения для каждого xlInsideHorizontal. Наконец, третий прогон завершается без каких-либо исключений.

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

Он будет работать с каждой строкой, обернутой в try, кроме блока. Я просто хотел что-то более короткое и более очевидное, потому что 20 строк, обернутых в свои собственные циклы try catch, просто позже вызывают проблемы.

Update2

Это очищенный CSV-файл для тестирования: Гист-файл

Заключение

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

Ответы [ 4 ]

12 голосов
/ 01 сентября 2011

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

class Suppressor:
    def __init__(self, exception_type):
        self._exception_type = exception_type

    def __call__(self, expression):
        try:
            exec expression
        except self._exception_type as e:
            print 'Suppressor: suppressed exception %s with content \'%s\'' % (type(self._exception_type), e)
            # or log.msg('...')

Затем отметьте в трассировке текущего кода, какое именно исключение возникло, и создайте подавитель только для этого исключения:

s = Suppressor(excel.WhateverError) # TODO: put your exception type here
s('excel.Selection.Borders(xlDiagonalDown).LineStyle = xlNone')

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

10 голосов
/ 01 сентября 2011

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

Но, конечно, вам нужно решение вашей проблемы. Вот мое предложение:

  1. Создайте класс-оболочку, который реализует все необходимые вам методы и делегирует их реальному экземпляру Excel.

  2. Добавьте декоратор перед каждым методом, который оборачивает метод в блок try except, и регистрируйте исключение. Никогда не глотайте исключения

Теперь код работает для вашего клиента, который покупает вас некоторое время, чтобы выяснить причину проблемы. Я предполагаю, что а) Excel не выдает полезное сообщение об ошибке, или б) код оболочки проглатывает настоящее исключение, оставляя вас в неведении, или в) метод Excel возвращает код ошибки (например, «ложь» для «не удалось») и вам нужно вызвать другой метод Excel, чтобы определить причину проблемы.

[РЕДАКТИРОВАТЬ] На основе приведенного ниже комментария, который сводится к «Моему боссу все равно, и я ничего не могу поделать»: вы упускаете важный момент: это ваши боссы обязанность принять решение, но ваша обязанность предоставить ей список вариантов вместе с плюсами / минусами, чтобы она могла принять правильное решение. Просто сидеть там и говорить: «Я ничего не могу сделать» - и вы попадете в беду, которую вы пытаетесь избежать.

Пример: * * тысяча двадцать-восемь

Решение 1: игнорировать ошибки

Pro: Наименьший объем работы Против: Есть вероятность, что полученные данные неверны или случайны. Если важные бизнес-решения основаны на нем, существует высокий риск того, что эти решения будут неверными.

Решение 2. Зарегистрируйте ошибки

Pro: Небольшой объем работы, пользователи могут начать быстро использовать результаты, тратит время на выяснение источника проблемы Con: «Если вы не можете исправить это сегодня, с чего вы взяли, что у вас будет время исправить это завтра?» Кроме того, вам может потребоваться много времени, чтобы найти источник проблемы, потому что вы не являетесь экспертом

Решение 3: Задайте вопрос эксперту

Найдите эксперта в этой области и помогите ему / ей найти / улучшить решение.

Pro: найдет решение намного быстрее, чем самостоятельно изучит все тонкости COM Против: Дорогой, но высокий шанс на успех. Также найдет проблемы, о которых мы даже не знаем.

...

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

Если вы выбираете решение № 2, переходите к подходу обертки. См. Документацию , как написать декоратор ( пример из IBM ). Это всего несколько минут работы, чтобы обернуть все методы, и это даст вам кое-что для работы.

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

[EDIT2] Вот некоторый код, который оборачивает «опасные» части во вспомогательный класс и делает обновление стилей более простым:

class BorderHelper(object):
    def __init__(self, excel):
        self.excel = excel

    def set( type, LineStyle = None, Weight = None, Color = None ):
        border = self.excel.Selection.Borders( type )

        try:
            if LineStyle is not None:
                border.LineStyle = LineStyle
        except:
            pass # Ignore if a style can't be set

        try:
            if Weight is not None:
                border.Weight = Weight
        except:
            pass # Ignore if a style can't be set

        try:
            if Color is not None:
                border.Color = Color
        except:
            pass # Ignore if a style can't be set

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

    borders = BorderHelper( excel )

    borders.set( xlDiagonalDown, LineStyle = xlNone )
    borders.set( xlDiagonalUp, LineStyle = xlNone )
    borders.set( xlEdgeLeft, LineStyle = xlContinuous, Weight = xlThin, Color = xlAutomatic )
    ...
4 голосов
/ 01 сентября 2011

Это просто упаковывает вызовы функций, но вы также можете расширить его для обработки доступа к атрибутам и для прокси-обработки результатов доступа к вложенным атрибутам, в конечном итоге просто заключив __setattr__ в блок try:except.

В вашем случае может быть целесообразно проглотить только некоторые конкретные типы исключений (как говорит @vsekhar).

def onErrorResumeNext(wrapped):
    class Proxy(object):
        def __init__(self, fn):
            self.__fn = fn

        def __call__(self, *args, **kwargs):
            try:
                return self.__fn(*args, **kwargs)
            except:
                print "swallowed exception"

    class VBWrapper(object):
        def __init__(self, wrapped):
            self.wrapped = wrapped

        def __getattr__(self, name):
            return Proxy(eval('self.wrapped.'+name))

    return VBWrapper(wrapped)

Пример:

exceptionProofBorders = onErrorResumeNext(excel.Selection.Borders)
exceptionProofBorders(xlDiagonalDown).LineStyle = xlNone
exceptionProofBorders(xlDiagonalup).LineStyle = xlNone
0 голосов
/ 01 сентября 2011

Вы можете архивировать аргументы из трех списков и делать следующее:

for border, attr, value in myArgs:
    while True:
        i = 0
        try:
            setattr(excel.Selection.Borders(border), attr, value) 
        except:
            if i>100:
                break
        else:
            break

Если ваши исключения полностью случайны, это будет пытаться до успеха (с ограничением в 100 попыток). Я не рекомендую это.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...