Catching the same result in every class method

I, a beginner, are working on a simple map-based GUI. written in Python. There is a base class, which, among other things, consists of a dictionary of all cards, for example _cards = {'card1_ID': card1, 'card2_ID': card2} . Maps in the GUI include their unique identifiers.

Since I plan to make the code acceptable to other newbies, I want to explicitly tell them if they gave a card identifier that does not exist (instead of throwing a KeyError). Now I have a thin repeat phrase try-expect, which makes me suspicious:

Part of the code, single-line methods without try-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 colour""" try: self._cards[card_ID].invert() except KeyError: raise ValueError("Invaild card ID") 

Is this accepted practice? Is there a better way to catch this KeyError in every class method?

+6
source share
4 answers

Extract the actual card from the identifier into a separate method using try / except there and call this method from around the world.

 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() ... 
+8
source

You can use a decorator to remove some of this repeating boiler plate.

 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 

Then you will use it as follows:

 @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 colour""" self._cards[card_ID].invert() 
+7
source

You can always use the decorator function to get what you want. This link is a great tutorial to learn about decorators and how to use them. I will give an example of a solution using decorators in your case.

Basically, you just create a function that takes a function as a parameter and returns a wrapper that does something special with it. One that might fit your decision would look like this:

 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 

... then you can decorate your functions / methods as follows:

 @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 colour""" self._cards[card_ID].invert() 

... for this is really syntactic sugar:

 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) 
+1
source

You might consider making your collection of cards into your own type of collection, which may provide the individual exceptions you require. If you use a dict for your map collection, you can make your own dict with custom behavior as follows:

 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)) 

Now you can simply use your various methods as follows:

  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. 

... just make sure you use your class for your ._cards attribute.

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

OR:

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

The best part is that your user interface class is not tied to any custom or unusual interface (i.e. getcard() ) for the object containing the data. The interface here is consistent with the Python data model . Therefore, if for some reason you decide that you want to use the same interface for another class of objects, you used a proven API that will translate almost everything that was written in a way compatible with the Python data model.

+1
source

All Articles