【问题标题】:Catching the same expection in every method of a class在类的每个方法中捕获相同的异常
【发布时间】:2016-11-14 21:37:03
【问题描述】:

我是一名初学者,正在开发一个简单的基于卡片的 GUI。用 Python 编写。有一个基类,其中包括所有卡片的词汇表,例如_cards = {'card1_ID': card1, 'card2_ID': card2}。 GUI 上的卡片由其唯一 ID 引用。

由于我计划使代码对其他初学者可用,我想明确告知他们是否提供了不存在的卡 ID(而不是抛出 KeyError)。现在我有大量重复的 try-expect 子句让我怀疑:

部分代码,一行方法无需try-catch:

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

这是一种公认​​的做法吗?有没有更好的方法在类的每个方法中捕获这个 KeyError ?

【问题讨论】:

  • 考虑decorator
  • 另一种方法是覆盖 _cards 实例的 _getitem_。
  • 当您想要的是ValueError 时,您是否有理由为您的卡片集合使用引发KeyError 的对象?或者,您为什么首先想要ValueError?为什么不在代码中使用类的位置捕获KeyError 而不是ValueError?如果问题是自定义错误消息,最好的办法是创建自己的 CardsCollection 类型,并内置自己的错误消息。

标签: python python-3.x exception exception-handling


【解决方案1】:

您可以考虑将您的卡片集合变成您自己的集合类型,以提供您需要的自定义例外。如果您将 dict 用于您的卡片收藏,您可以使用如下自定义行为创建自己的 dict:

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

现在您可以像这样执行各种方法:

    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. 

...只需确保将您的类用于您的._cards 属性。

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

或:

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

这样做的好处是,您的 UI 类不会为保存数据的对象提供某种自定义或不寻常的接口(即getcard())。这里的界面与Python data model一致。因此,如果出于某种原因,您决定要对另一个对象类使用相同的 UI,那么您已经使用了经过验证的 API,它可以翻译几乎所有以与 Python 一致的方式编写的内容数据模型。

【讨论】:

    【解决方案2】:

    你总是可以使用装饰器函数来得到你想要的。 This link 是学习装饰器是什么以及如何使用它们的绝佳教程。在您的情况下,我将给出一个使用装饰器的示例解决方案。

    基本上,您只需创建一个函数,该函数将函数作为参数并返回对其执行特殊操作的包装器。可能适合您的解决方案的一个如下所示:

    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
    

    ...然后你可以像这样装饰你的函数/方法:

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

    ...这实际上只是语法糖:

    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)
    

    【讨论】:

    • 感谢您的链接和解释,他们帮助了很多。
    • @Neinstein:如果有帮助,别忘了点赞或标记为答案!
    【解决方案3】:

    将卡片的实际获取从 id 提取到一个单独的方法中,并在那里使用 try/except,然后从其他任何地方调用该方法。

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

    【讨论】:

    • 我会将方法命名为 get_card()(它不会返回 card_id 吗?)...
    • @brunodesthuilliers 你当然是对的,更新了。
    • 老实说,我不知道为什么我没有想到这一点...谢谢!
    【解决方案4】:

    您可以使用装饰器来删除一些重复的样板。

    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
    

    然后你会这样使用它:

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

    【讨论】:

    • 虽然对于 OP 的用例来说这是一个完全有效的答案(对于 python 新手来说也是一个很好知道的功能),但我宁愿使用 Daniel Roseman 的更明显的解决方案......
    • @brunodesthuilliers 同意这两个声明。我很高兴让装饰师知道,在任何其他情况下我都会接受这个答案。我现在选择 Daniel Rosemann 的,因为这就是我要实际实现的,但我更喜欢这个。
    猜你喜欢
    • 1970-01-01
    • 2011-12-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-05-12
    • 1970-01-01
    相关资源
    最近更新 更多