如何在调用 __init__ 之前检查参数类型

How to check an argument type before __init__ gets called

我需要检查 __init__() 中的参数类型。我是这样做的:

class Matrix:

    def __init__(self, matrix):
        """
        if type(matrix) != list raise argument error
        """
        if type(matrix) != list:
            raise TypeError(f"Expected list got {type(matrix)}")
        self.matrix = matrix


a = 5
new_matrix = Matrix(a)

但是当 TypeError 被引发时 __init__() 方法已经被调用。我想知道在它被调用之前如何做到这一点。我认为这可以使用 metaclass 在某些时候“拦截它”并引发错误来完成,但我不知道如何做到。

首先: 人们通常不会在 Python 中进行这样的 运行 时间检查 - 除非绝对必要。这个想法是,在这种情况下,无论传递给 __init__ 什么都将表现得与列表足够相似,可以代替它使用。这就是“鸭子打字”的思想。

第二个: 如果确实有必要进行此检查,那么就像您所做的那样,在函数或方法体内使用 if 语句就是执行此操作的方法。 “方法是 运行 并在其中引发错误”并不重要 - 这就是动态类型在 Python.

中的工作方式

第三: 实际上有一种方法可以防止您的程序使用不正确的参数类型调用 __init__,那就是使用静态类型检查。当您 运行 检查器(例如“mypy”)时,您的程序将在准备步骤上出错 - 这与一些静态语言会引发错误的时间大致相同:当它们在之前的显式步骤中编译时正在 运行。静态类型检查可以增加您认为需要的安全性 - 但在 Python 中编写代码是一个充满样板和官僚主义的全新世界。网络搜索“python 静态类型检查”可以列出一些要点,您可以开始学习 - 第二个 link 我觉得很有趣:https://realpython.com/python-type-checking/

第四个: 如果你选择 if-based 检查,你应该检查你得到的对象是否是“足够的列表”,而不是“type(a) != list”。这将禁止 sub类 个列表。 not isinstance(a, list) 将接受 list sub类,但会阻止其他几种可能正常工作的对象类型。根据您的需要,您的代码将适用于任何“序列”类型。在这种情况下,您可以导入 collections.abc.Sequence 并检查您的参数是否是该参数的实例 - 这将允许您的方法的用户使用任何具有长度的 类 并且可以按顺序检索 itens .

并且,再重复一遍:在方法内部进行此检查绝对没有问题。它可以通过创建一个可以进行类型检查的复杂装饰器来分解——实际上有 Python 包可以使用类型注释,就像它们被静态类型检查工具使用一样,并且 运行时间检查。但这不会为您赢得任何执行时间。静态类型检查将在 运行ning 之前完成,但是由此获得的资源可以忽略不计。

最后,不,这与元类无关。可以使用元类向您的所有方法添加装饰器,并让这些装饰器执行 运行 时间检查 - 但您可能只是显式地使用装饰器。