Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Catching the same expection in every method of a class

I, a beginner, am working on a simple card-based GUI. written in Python. There is a base class that, among other things, consists a vocabulary of all the cards, like _cards = {'card1_ID': card1, 'card2_ID': card2}. The cards on the GUI are referenced by their unique IDs.

As I plan to make the code avabile for other beginners, I want to explicitly inform them if they gave a card ID that does not exists (instead of throwing a KeyError). Now I have a ton of repetitive try-expect clause that makes me suspicious:

Part of the code, one-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's colour"""

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

Is this an accepted practice? Are there any better way to catch this KeyError in every method of the class?

like image 613
Neinstein Avatar asked Jul 12 '16 14:07

Neinstein


2 Answers

Extract the actual getting of the card from the id into a separate method, with a try/except there, and call that method from everywhere else.

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

...
like image 154
Daniel Roseman Avatar answered Sep 21 '22 10:09

Daniel Roseman


You could use a decorator to remove some of that repetitive 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 would use it like this:

@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()
like image 28
willnx Avatar answered Sep 20 '22 10:09

willnx