在Python中,何时应该使用“assert”?

5

为了提供一些背景,我正在使用Doyren库(libtcod)在Python中开发一个Roguelike游戏。我更习惯于C++,其中对象具有强类型。

我正在编写多个类,例如GameMap、GameObject等。许多这些类包含期望特定类型的方法,例如:

class GameMap:
    ...
    def add_object(self, game_object, x, y):
        ...

该方法将游戏对象game_object添加到地图上的坐标(x, y)。该函数很明显会有几种被误用的情况:
  • game_object传递了一个非GameObject对象
  • x或y传递了一个非整数
  • x或y传递了一个负整数
  • x传递了一个超出地图宽度的整数
  • y传递了一个超出地图高度的整数
我的问题是:处理方法误用的Pythonic方式是什么?
我看到有几种可能性: 选项1:在方法开始处设置一系列断言:
def add_object(self, game_object, x, y):
    assert(isinstance(game_object, GameObject)
    assert(type(x) == type(y) == int)
    assert(0 <= x < self.map_width and 0 <= y < self.map_height)
    ...

由于我需要将它们复制粘贴到GameMap中的许多方法中,因此这些断言变得非常重复,这就是为什么我还提供了第二个选项:

选项2:编写自己的函数以编写断言,并在需要时调用这些函数以防止复制和粘贴。

def check_game_object(self, game_object):
    assert(isinstance(game_object, GameObject)

def check_coordinate(self, x, y):
    assert(type(x) == type(y) == int)
    assert(0 <= x < self.map_width and 0 <= y < self.map_height)

def add_object(self, game_object, x, y):
    check_game_object(game_object)
    check_coordinate(x, y)
    ...

选项3:在方法开头设置一系列自定义异常:
def add_object(self, game_object, x, y):
    if not isinstance(game_object, GameObject):
        raise InvalidParameterException("game_object not a GameObject")
    elif not type(x) == type(y) == int:
        raise InvalidParameterException("(x, y) not integers")
    elif not (0 <= x < self.map_width and 0 <= y < map.self_height)
        raise InvalidMapCell("x, y do not represent a valid map cell)
    ...

选项4:返回失败指示器,并在更高层面上处理问题

def add_object(self, game_object, x, y):
    if not isinstance(game_object, GameObject):
        return False
    elif not type(x) == type(y) == int:
        return False
    elif not (0 <= x < self.map_width and 0 <= y < map.self_height)
        return False
    ...

选项X:其他选择?

这里的任何建议都将非常感激!我想确保在继续进行时遵循有用和可维护的模式。


2
断言并不是用于验证参数的。它们用于确保外部非用户对象符合预期。 - Ignacio Vazquez-Abrams
谢谢您的建议。是否有一种Pythonic的替代方法来强制正确的方法使用? - Ashley
3
在Python中通常的做法是直接使用对象并捕获任何可能发生的异常。 - Ignacio Vazquez-Abrams
4
断言并不用于验证数据,对于这个目的可以使用ValueError(或其派生异常)。断言用于验证程序逻辑的正确性,换句话说,它们用于测试如果逻辑正确,就应该永远不会发生的事情。因此,如果引发了断言错误,这意味着您需要更改该逻辑。 - PM 2Ring
1个回答

7
断言的作用是确保对象、结果、返回值等符合我们的期望。虽然它们可以用于变量类型检查,但这并不是它们的真正目的,而且会变得重复。
在您的情况下,我建议使用 Python EAFP 的方式来做事情。让操作在函数输入上执行,并在不符合预期时捕获异常。来自Python 词汇表
EAFP:宁愿请求原谅,也不要事先征求同意。这种常见的 Python 编码风格假定存在有效的键或属性,并在假设被证明为 false 时捕获异常。这种清晰快速的风格的特点是有许多 try 和 except 语句。这种技术与许多其他语言(如 C)常见的 LBYL(先判断后跳转)风格形成对比。
一个快速的例子:
def f(x):
    """If x is str a TypeError is raised"""
    return 1 + x

try:
    f('a')
except TypeError as e:
    # something here or raise a custom exception
    raise

非常感谢,这正是我想要的。 - Ashley
为什么不试一下:_=1+"a"; 而使用这个函数呢? - mazunki
3
@mazunki 这只是一个举例而已。 - Amit Tripathi
当然可以,但是创建一个一次性函数似乎是不必要的,而且过于复杂/混乱,而将其分配给_同样具有更好的可视化理解性,甚至更好。 - mazunki

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接