2011-09-01 4 views
8

Attualmente sto eseguendo l'automazione Python di Excel con com. È perfettamente funzionante e fa ciò che voglio, ma ho scoperto qualcosa di sorprendente. A volte, alcuni dei comandi di Excel che uso falliscono con un'eccezione senza un motivo apparente. Altre volte, funzioneranno.Python clean way per racchiudere le singole istruzioni in una prova tranne il blocco

Nel codice equivalente VB per quello che sto facendo, questo problema è apparentemente considerato normale, ed è intonacato con una dichiarazione On Error Resume Next. Python non ha detto affermazione, ovviamente.

Non riesco a concludere l'intero set in un ciclo try except, perché potrebbe "fallire" a metà e non essere completato correttamente. Quindi, quale sarebbe un modo pitone per avvolgere diverse affermazioni indipendenti in un tentativo tranne il blocco? In particolare, qualcosa di più pulito di:

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

Il codice in questione è il bit 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 

Aggiornamento:

Forse è necessario un po 'di spiegazioni sul bit di errore. Due esecuzioni identiche sulla mia macchina di prova, con codice identico, sullo stesso file, producono lo stesso risultato. Una corsa genera eccezioni per ogni riga xlInsideVertical. L'altra genera eccezioni per ogni xlInsideHorizontal. Infine, una terza prova completa senza eccezioni.

Per quanto mi riguarda posso dire Excel considera questo comportamento normale, perché sto clonazione del codice VB costruito da macro generatore di Excel, non il codice VB prodotto da una persona. Questa potrebbe essere un'assunzione errata, ovviamente.

Funzionerà con ogni linea racchiusa in una prova tranne blocco Volevo solo qualcosa di più breve e più ovvio, perché 20 linee avvolte nei propri tentativi di cattura loop è solo per chiedere guai in seguito.

Update2:

Questo è un file CSV fregati per il test: gist file

Conclusione:

La risposta fornita da Vsekhar è perfetto. Elimina la soppressione delle eccezioni, in modo che in seguito, se e quando avrò tempo, posso effettivamente gestire le eccezioni man mano che si verificano. Consente inoltre di registrare le eccezioni in modo che non scompaiano, non interrompe altre eccezioni ed è abbastanza piccolo da essere facilmente gestibile tra sei mesi.

+1

Che ne dici di non farlo fallire in primo luogo? Python non è PHP o Visual Basic e sfortunatamente impone una sana gestione degli errori :( –

+0

Potresti salvare tutte le chiamate in un elenco e poi scorrere su di esse con una singola istruzione try nel ciclo? Solo non so come "salvare" questo tipo di chiamate: – rplnt

+0

@rpInt: le istruzioni non sono oggetti di prima classe in Python - non possono essere assegnate a variabili e passate in funzioni –

risposta

12

consideri astraendo via la soppressione. E per il punto di Aaron, non ingoiare eccezioni in generale.

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('...') 

Poi, nota nel traceback del codice corrente esattamente ciò che viene sollevata un'eccezione, e creare un soppressore solo per questa eccezione:

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

questo modo si ottiene riga per riga di esecuzione (quindi i tuoi traceback saranno comunque utili) e stai sopprimendo solo le eccezioni che intendevi esplicitamente. Altre eccezioni si propagano come al solito.

+0

Questo è molto nello spirito di ciò che Aaron Digulla stava suggerendo, tranne che tu * stai * "inghiottendo" le eccezioni * designate * in silenzio. Sei effettivamente corretto per * evitare * sopprimere le eccezioni per impostazione predefinita, ma IMO sarebbe meglio almeno registrare le eccezioni che * fai * sopprimono, o memorizzarle in una struttura dati che può essere scaricata alla fine dell'esecuzione del programma in modo tale puoi vedere cosa è realmente successo. – Peter

+1

@Peter: abbastanza corretto, ho aggiunto il comportamento di registrazione – vsekhar

10

Le eccezioni non si verificano mai "senza motivo apparente". C'è sempre una ragione e questa ragione deve essere risolta. Altrimenti, il tuo programma inizierà a produrre dati "casuali" in cui "random" è in balia del bug che stai nascondendo.

Ma ovviamente, hai bisogno di una soluzione per il tuo problema. Qui è il mio suggerimento:

  1. creare una classe wrapper che implementa tutti i metodi di cui avete bisogno e li delegati alla vera istanza di Excel.

  2. Aggiungere un decoratore prima di ogni metodo che avvolge il metodo in un blocco try except e registrare l'eccezione. Mai ingoiare eccezioni

Ora il codice funziona per il vostro cliente il quale si acquista un po 'di tempo per scoprire la causa del problema. La mia ipotesi è che a) Excel non produce un messaggio di errore utile o b) il codice del wrapper ingoia la vera eccezione lasciandoti al buio o c) il metodo Excel restituisce un codice di errore (come "false" per "fallito") e devi chiamare un altro metodo Excel per determinare qual è la causa del problema.

[EDIT] Sulla base del commento qui sotto, che si riducono a "Il mio capo non si cura e non c'è niente che io possa fare": Ti manca un punto cruciale: E 'vostri capi dovere fare la decisione ma è il tuo dovere di darle un elenco di opzioni insieme a pro/contro in modo che possa prendere una decisione corretta. Stare lì seduto dicendo "Non posso fare nulla" ti porterà nei guai che stai cercando di evitare.

Esempio:

Soluzione 1: ignorare gli errori

Pro: minor quantità di lavoro Con: C'è la possibilità che i dati risultanti è sbagliato o casuale. Se le decisioni aziendali importanti sono basate su di esso, c'è un alto rischio che tali decisioni siano sbagliate.

Soluzione 2: Log degli errori

Pro: Piccola quantità di lavoro, gli utenti possono iniziare a utilizzare i risultati in modo rapido, compra tempo per capire l'origine del problema Con: "Se non è possibile risolvere il problema oggi, cosa ti fa pensare che avrai tempo per sistemarlo domani? " Inoltre, potrebbe prendere molto tempo per trovare la fonte del problema, perché non sei esperto

Soluzione 3: Chiedi all'esperto

trovare un esperto nel campo e aiutarlo/lei a dare un'occhiata/migliorare la soluzione.

Pro: otterrà una soluzione molto più rapidamente di apprendere i dettagli di COM stesso Contro: costoso ma alta probabilità di successo. Troveremo anche problemi che non sappiamo nemmeno.

...

Penso che si veda il modello. I capi prendono decisioni sbagliate perché noi (volontariamente) li lasciamo. Qualsiasi capo nel mondo è felice per fatti concreti e input quando devono prendere una decisione (beh, chi non dovrebbe non essere il capo, quindi questo è un modo sicuro per sapere quando iniziare a cercare un nuovo lavoro) .

Se si seleziona la soluzione n. 2, andare per l'approccio wrapper. See the docs come scrivere un decoratore (example from IBM). Sono solo pochi minuti di lavoro per completare tutti i metodi e ti darà qualcosa su cui lavorare.

Il passaggio successivo è creare un esempio più piccolo che a volte fallisce e quindi postare domande specifiche su Python, Excel e sul wrapper COM qui per capire il motivo dei problemi.

[EDIT2] Ecco alcuni codice che avvolge le parti "pericolose" in una classe di supporto e rende l'aggiornamento degli stili più semplici:

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 

Usage:

borders = BorderHelper(excel) 

    borders.set(xlDiagonalDown, LineStyle = xlNone) 
    borders.set(xlDiagonalUp, LineStyle = xlNone) 
    borders.set(xlEdgeLeft, LineStyle = xlContinuous, Weight = xlThin, Color = xlAutomatic) 
    ... 
+4

Penso che quella parola non significhi cosa tu pensi che significhi. C'è sempre una ragione, spesso non è "apparente". – agf

+0

Non sono riuscito a trovare una formulazione migliore per esprimere il mio disprezzo per l'ignoranza :-) –

+0

@Aaron Digulla Ho aggiornato la mia domanda con un po 'più di informazioni di base. Se ne sai abbastanza di programmazione excel e com per far luce su questo argomento, ti sarei grato. Ma nel mio caso, 20 minuti di lettura su com, e 15 minuti di lettura degli esempi com Python semplicemente non copre il problema di spazio sufficiente per il debug. Mngt lo vuole in un giorno, non dopo che ho imparato com ed eseguire il debug di Excel. –

4

Questo avvolge solo le chiamate di funzioni, ma è possibile estenderlo per gestire anche l'accesso agli attributi e per proxy i risultati degli accessi di attributi nidificati, infine basta avvolgere lo __setattr__ nel blocco try:except.

Potrebbe essere sensato ingoiare solo alcuni tipi di eccezione specifici nel tuo caso (come @vsekhar dice).

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) 

Esempio:

exceptionProofBorders = onErrorResumeNext(excel.Selection.Borders) 
exceptionProofBorders(xlDiagonalDown).LineStyle = xlNone 
exceptionProofBorders(xlDiagonalup).LineStyle = xlNone 
0

è possibile comprimere gli argomenti da tre lista, e procedere come segue:

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 

Se le eccezioni sono trully a caso, questo proverà fino successo (con un limite di 100 tentativi). Non lo consiglio.