我应该检查类属性的输入类型吗?

Should I check the type of input to my class properties?

提问人:Parikshit 提问时间:9/10/2023 最后编辑:Parikshit 更新时间:9/10/2023 访问量:50

问:

我是一个相当新的Python开发人员,并且已经习惯了“鸭子打字”的想法。

我在另一个模块中使用了一个类来对它执行特定操作,例如,访问它的名称和版本。这个模块显然不能处理任意值,需要名称是字符串,版本是整数。

目前,我正在使用 isinstance() 强制执行类属性的输入类型:

class ExampleObject(object):
    def __init__(self):
        self._name = None
        self._version = None

    @property
    def name(self):
        return self._name

    @name.setter
    def name(self, name):
        if not isinstance(name, str) and name is not None:
            raise ValueError("Expected string, got {0}".format(type(name)))
        
        self._name = name

    @property
    def version(self):
        return self._name

    @version.setter
    def version(self, version_number):
        if not isinstance(version_number, int) and version_number is not None:
            raise ValueError("Expected int, got {0}".format(type(version_number)))

        self._version = version_number
        

如果我采用 Duck 类型的想法,我不应该对我的属性强制执行特定类型。如果我放弃了类型检查,而其他一些开发人员开始将这些属性用于其他数据类型,则可能导致我编写的模块中断。基本上,模块期望一个整数,它得到其他东西。正确的方法是什么?

我检查了几个与此相关的问题,但没有一个涉及如果我们跳过类型检查会发生什么,以及如何强制其他开发人员不会破坏某些东西,因为他们不知道另一个模块使用这些属性来执行特定于类型的事情。

Python 属性 duck-typing code-structure

评论

1赞 Stephen C 9/10/2023
“请注意,我只能使用 python 2 来尝试任何事情。” - Python 2 自 2020 年 1 月 1 日起生命周期结束。如果你试图维护/开发一个 Python 2(仅限)的应用程序,你只是在更深地挖自己的坑。如果你不能将 Python 3 与 Python 2 并排安装来尝试一下,那么你的平台有问题!
0赞 Parikshit 9/10/2023
嘿,@StephenC,我自己想切换到 python 3,但我不确定我的代码是否与其他软件兼容。就上下文而言,我正在开发一个类,该类足够通用,可以在多个地方或具有自己的 python 版本的软件中使用。我需要它的地方默认运行 python 3.7,这不是问题,但还有其他软件可以使用在 python 2 上运行的此类。这些是第三方软件,我无法控制它们的原生 python 版本。
0赞 Stephen C 9/10/2023
“我自己想切换到python 3,但我不确定我的代码是否与其他软件兼容。- 找出答案的最好(实际上唯一)方法是尝试一下。“......但是还有其他软件可以使用在 Python 2 上运行的此类。- 从长远来看,实际的解决方案是让它成为他们的问题。声明您的代码需要 Python 3.7 或更高版本。如果“他们”想要在生命周期结束的 Python 版本上运行它,那是他们的问题,而不是你的问题。(除非“他们”付给你很多钱来做额外的工作来支持他们的遗留需求。
0赞 Parikshit 9/10/2023
@StephenC,很好,谢谢你指出这一点。我什至没有考虑这些术语,现在我专注于此,我在 Python 2 中写了很多东西,而没有以兼容性的名义考虑它是否真的有必要。我将切换到 Python 3,看看会发生什么。我将删除问题中的 Python 2 注释。

答:

1赞 lucs100 9/10/2023 #1

Python 的鸭子类型让你有责任强制执行它。如果参数将由用户传递,则最好执行强制转换和/或类型验证。例如,您可以尝试使用内置方法转换为 int。如果此操作失败(即。 cannot be converted to a string),该方法会为您引发一个 ValueError,您可以按如下方式检查:version_numberint()version_numberint()

@version.setter
def version(self, version_number):
    try:
        version_number = int(version_number)
    except ValueError: #when casting to int fails, Python raises a ValueError
        # handle error here
    #otherwise, version_number is now a valid int, so continue

如果你是唯一一个使用你的代码的人,你可以放弃此验证,风险由你自己承担。无论您选择哪种方式,使用类型提示也是一个好主意。类型提示在运行时不执行任何操作(因此它们不会为您执行任何强制转换/验证),但可以帮助 IDE 提醒您参数或返回值的预期类型。函数的类型提示可能如下所示:

@version.setter
def version(self, version_number: int) -> None:

该参数不需要任何类型提示,因为 IDE 可以看出 将是 .参数上的类型提示指示此参数应为 int。最后,返回类型提示 表示此函数不返回任何内容。selfselfExampleObjectintversion_numberNone

来源: https://mypy.readthedocs.io/en/stable/cheat_sheet_py3.html

评论

0赞 Parikshit 9/10/2023
嘿,抱歉,但我忘了提到我只能将 python 2 用于我的代码,因此类型提示不是一种选择。另外,铸造不是在以一种回合的方式执行类型吗?不管怎样,我仍在强制执行它,那么为什么不使用更便宜的 isinstance() 呢?
0赞 Parikshit 9/10/2023
我决定放弃 python 2 并使用 python 3,但我对铸造的问题仍然存在。所以让我知道你的想法。
0赞 lucs100 9/10/2023
@Parikshit 这取决于您希望如何处理错误。如果要使用自定义消息引发 ValueError(在控制台中显示或在调用方法时进行处理),则原始方法将起作用。如果您希望在方法中优雅地处理它,则可以使用 try/catch 范例来避免未捕获的异常。
0赞 Parikshit 9/13/2023
明白了。感谢您的帮助。