2016-07-12 110 views
6

I, un principiante, sto lavorando su una semplice GUI basata su schede. scritto in Python. Esiste una classe base che, tra le altre cose, consiste in un vocabolario di tutte le carte, come _cards = {'card1_ID': card1, 'card2_ID': card2}. Le carte sulla GUI sono referenziate dai loro ID univoci.Catching della stessa aspettativa in tutti i metodi di una classe

Poiché ho intenzione di rendere il codice disponibile per altri principianti, desidero informarli esplicitamente se hanno fornito un ID tessera che non esiste (invece di lanciare un KeyError). Ora ho un sacco di ripetitivo clausola try-aspettarsi che mi rende sospettoso:

parte del codice, i metodi a una linea senza provare-catching:

def shift(self, card_ID, amount): 
     """Moves the card by the given amount of pixels. 
      :param amount: the horizontal and vertical amount of shifting in pixels; tuple""" 

     try: 
      self._cards[card_ID].shift(amount) 
     except KeyError: 
      raise ValueError("Invaild card ID") 


    def align(self, card_ID, horizontal, vertical): 
     """Aligns the card to the given position.""" 

     try: 
      card = self._cards[card_ID] 
      card.align(horizontal, vertical) 
     except KeyError: 
      raise ValueError("Invaild card ID") 


    def invert(self, card_ID): 
     """Inverts the card's colour""" 

     try: 
      self._cards[card_ID].invert() 
     except KeyError: 
      raise ValueError("Invaild card ID") 

È questo una pratica accettata? C'è un modo migliore per catturare questo KeyError in ogni metodo della classe?

+1

Considerare un [decoratore] (http://thecodeship.com/patterns/guide-to-python-function-decorators/) – khelwood

+0

Un altro approccio verrà sovrascritto \ __ getitem \ __ dell'istanza _cards. –

+0

C'è un motivo per cui usi un oggetto per la tua collezione di carte che solleva un "KeyError" quando quello che vuoi è un "Errore di Valore"? O in alternativa, perché vuoi un 'ValueError' in primo luogo? Perché non catturare un "KeyError" piuttosto che un "ValueError" nel punto del codice che sta facendo uso della tua classe? Se il problema è il messaggio di errore personalizzato, probabilmente la cosa migliore da fare è creare il proprio tipo 'CardsCollection' con il proprio messaggio di errore integrato. –

risposta

8

Estrarre l'effettiva acquisizione della carta dall'ID in un metodo separato, con una prova/eccetto lì e chiamare tale metodo da qualsiasi altra parte.

def get_card(self, card_id): 
    try: 
     return self._cards[card_ID] 
    except KeyError: 
     raise ValueError("Invaild card ID") 

def invert(self, card_id): 
    return self.get_card(card_id).invert() 

... 
+0

Avrei chiamato il metodo 'get_card()' (non restituisce un card_id lo fa?) ... –

+0

@brunodesthuilliers hai ragione, ovviamente, aggiornato. –

+0

Onestamente, non ho idea del perché non ci ho pensato ... grazie! – Neinstein

7

È possibile utilizzare un decoratore per rimuovere alcuni di quella piastra di caldaia ripetitivo.

from functools import wraps 

def replace_keyerror(func): 
    """Catches KeyError and replaces it with ValueError""" 

    @wraps(func) 
    def inner(*args, **kwargs): 
     try: 
      func(*args, **kwargs) 
     except KeyError: 
      raise ValueError("Invaild card ID") 
    return inner 

Quindi si usa in questo modo:

@replace_keyerror 
def align(self, card_ID, horizontal, vertical): 
"""Aligns the card to the given position.""" 
    card = self._cards[card_ID] 
    card.align(horizontal, vertical) 

@replace_keyerror 
def invert(self, card_ID): 
    """Inverts the card's colour""" 
    self._cards[card_ID].invert() 
+3

Sebbene sia una risposta perfettamente valida (e una caratteristica interessante per i principianti di Python) per il caso d'uso dell'OP preferirei usare la soluzione molto più ovvia di Daniel Roseman ... –

+0

@brunodesthuilliers Concordato in entrambe le dichiarazioni. Sono contento di sapere i decoratori e in tutti gli altri casi accetterei questa risposta. Ora scelgo Daniel Rosemann perché è quello che effettivamente implememt, ma mi piace di più. – Neinstein

1

è sempre possibile utilizzare una funzione di decoratore per ottenere quello che vuoi. This link è un eccellente tutorial per imparare cosa sono i decoratori e come usarli. Darò una soluzione di esempio usando decoratori nel tuo caso.

Fondamentalmente, è sufficiente creare una funzione che accetta una funzione come parametro e restituisce il wrapper che fa qualcosa di speciale con esso. Uno che potrebbe andare bene la soluzione sarebbe simile a questa:

def catch_invalid_card_exception(func): 
    def wrapper(*args, **kwargs):  
     try: 
      return func(*args, **kwargs) 
     except KeyError: 
      raise ValueError("Invalid card ID") # Not "invaild" ;) 
    return wrapper 

... allora si può decorare le vostre funzioni/metodi come questo:

@catch_invalid_card_exception 
def shift(self, card_ID, amount): 
    """Moves the card by the given amount of pixels. 
     :param amount: the horizontal and vertical amount of shifting in pixels; tuple""" 
    self._cards[card_ID].shift(amount) 

@catch_invalid_card_exception 
def align(self, card_ID, horizontal, vertical): 
    """Aligns the card to the given position.""" 
    card = self._cards[card_ID] 
    card.align(horizontal, vertical) 

@catch_invalid_card_exception 
def invert(self, card_ID): 
    """Inverts the card's colour""" 
    self._cards[card_ID].invert() 

... che è lo zucchero in realtà solo sintattico per questo :

def shift(self, card_ID, amount): 
    # ... 
shift = catch_invalid_card_exception(shift) 

def align(self, card_ID, horizontal, vertical): 
    # ... 
align = catch_invalid_card_exception(align) 

def invert(self, card_ID): 
    # ... 
invert = catch_invalid_card_exception(invert) 
+0

Grazie per il link e la spiegazione, hanno aiutato molto. – Neinstein

+0

@Neinstein: Se ti è stato utile, non dimenticare di svenderlo o contrassegnarlo come risposta! –

1

Si può considerare di rendere la propria collezione di carte nel proprio tipo di raccolta in grado di fornire le eccezioni personalizzate necessarie. Se si utilizza un dict per la vostra collezione le carte, si potrebbe fare il vostro proprio dict con comportamento personalizzato come questo:

class CardsCollection(dict): 
    '''A dict-like collection of cards''' 
    def __getitem__(self, key): 
     try: 
      # first try default behavior 
      super().__self__(key) 
     except KeyError: 
      # it didn't work! 
      raise ValueError("Invalid card ID: {!r}".format(key)) 

Ora si può solo fare i vostri vari metodi come questo:

def align(self, card_ID, horizontal, vertical): 
     """Aligns the card to the given position.""" 
      card = self._cards[card_ID] 
      card.align(horizontal, vertical) 
etc. etc. 

.. . assicurati di utilizzare la classe per l'attributo ._cards.

_cards = CardsCollection(card1_ID = card1, card2_ID = card2) 

O:

_cards = CardsCollection({'card1_ID': card1, 'card2_ID': card2}) 

La cosa bella di questo è che la classe UI non si impegna a qualche tipo di interfaccia personalizzata o inusuali (vale a dire, getcard()) per l'oggetto che contiene i dati. L'interfaccia qui è coerente con lo Python data model. Pertanto, se per qualche motivo decidi che vorresti usare la stessa UI per un'altra classe di oggetti, hai usato un'API sperimentata e vera che tradurrà praticamente tutto ciò che è stato scritto in modo coerente con Python modello di dati.