11

让我们举一个 API 的例子

def get_abs_directory(self, path):
    if os.path.isdir(path):
       return path
    else:
       return os.path.split(os.path.abspath(path))[0]

我的问题是验证参数的pythonic方式是什么,我是否应该忽略任何类型的验证(我观察到所有python代码根本没有验证)

  1. 我应该检查“路径”是否为空且不为空
  2. 我应该检查路径的“类型”是否始终为字符串
  3. 一般来说,我应该检查参数类型吗?(我猜不像动态类型的python)

此问题并非针对 File IO,而 FileIO 仅用作示例

4

4 回答 4

14

正如此处的文档所述,Python 遵循EAFP方法。这意味着我们通常会使用更多的trycatch块,而不是尝试验证参数。让我演示一下:

import os


def get_abs_directory(path):
    try:
        if os.path.isdir(path):
            return path
        else:
            return os.path.split(os.path.abspath(path))[0]
    except TypeError:
        print "You inserted the wrong type!"


if __name__ == '__main__':
    get_abs_directory(1)  # Using an int instead of a string, which is caught by TypeError

但是,您可能希望以 LBYL(在您跳跃之前查看)样式进行编码,这看起来像这样:

import os


def get_abs_directory(path):

    if not isinstance(path, str):
        print "You gave us the wrong type, you big meany!"
        return None

    if os.path.isdir(path):
        return path
    else:
        return os.path.split(os.path.abspath(path))[0]

if __name__ == '__main__':
    get_abs_directory(1)
于 2013-10-21T07:24:51.177 回答
6

EAFP是 Python 中针对此类情况的事实上的标准,同时,如果您愿意,没有什么能阻止您一直遵循LBYL 。

但是,在适用EAFP时有保留:

  • 当代码仍然能够处理异常情况、在某些时候中断或使调用者能够验证可能的错误时,最好遵循EAFP原则。

  • 当使用EAFP导致静默错误时,显式检查/验证 ( LBYL ) 可能是最好的。

关于这一点,有一个 Python 模块parameters-validation可以在您需要时简化函数参数的验证:

@validate_parameters
def register(
    token: strongly_typed(AuthToken),
    name: non_blank(str),
    age: non_negative(int),
    nickname: no_whitespaces(non_empty(str)),
    bio: str,
):
    # do register

免责声明:我是项目维护者。

于 2019-04-26T17:00:03.317 回答
4

即使已经回答,这对于评论来说太长了,所以我将添加另一个答案。

一般来说,进行类型检查有两个原因:确保您的函数真正完成,并避免因错误输出而导致难以调试的下游故障。

对于第一个问题,答案总是合适的——EAFP 是正常的方法。而且您不必担心输入错误。

对于第二个......答案取决于您的正常用例,您确实担心输入错误/错误。当错误输入总是产生异常时(其中“错误输入”可以限制为您的应用程序可能期望产生的错误输入类型),EAFP 仍然是合适的(并且它更容易,更易于调试)。但是,如果错误的输入可能会产生有效的输出,那么 LYBL 可能会让您以后的生活更轻松。

示例:假设您调用 square(),将此值放入字典中,然后(很多)稍后从字典中提取此值并将其用作索引。当然,索引必须是整数。

square(2) == 4,是一个有效的整数,所以是正确的。square('a') 总是会失败,因为 'a'*'a' 是无效的,并且总是会抛出异常。如果只有这两种可能性,那么您可以安全地使用 EAFP。如果你确实得到了错误的数据,它会抛出一个异常,生成一个回溯,你可以用 pdb 重新启动,并很好地指示出了什么问题。

但是...假设您的应用程序使用了一些 FP。并且有可能(假设你有一个错误!当然不是正常操作)你不小心调用了 square(1.43)。这将返回一个有效值 - 2.0449 左右。您不会在这里遇到异常,因此您的应用程序会很乐意将 2.0449 放入字典中。很久以后,您的应用程序会将这个值从字典中拉出,将其用作列表的索引,然后 - 崩溃。您将获得回溯,您将使用 pdb 重新启动,并意识到这对您根本没有帮助,因为该值是很久以前计算的,您不再拥有输入,也不知道该数据是如何获得的那里。这些调试起来并不有趣。

在这些情况下,您可以使用断言(LYBL 的特殊形式)更早地检测这些类型的错误,或者您可以明确地进行。如果您没有调用该函数的错误,那么任何一个都可以工作。但是如果你这样做了......那么你真的会很高兴你在接近失败时人为地检查了输入,而不是自然而然地在你的应用程序中的某个随机位置。

于 2013-10-21T14:01:08.110 回答
1

正如此测试代码所示,代码确实“捕获”错误,引发异常以传入 None

import os.path
import os


class pathetic(unittest.TestCase):
    def setUp(self):
        if (not(os.path.exists("ABC"))):
            os.mkdir("ABC")
        else:
            self.assert_(False, "ABC exists, can't make test fixture")

    def tearDown(self):
        if (os.path.exists("ABC")):
            os.rmdir("ABC")

    def test1(self):
        mycwd = os.path.split(os.path.abspath(os.getcwd()))[0]
        self.assertEquals("/", self.get_abs_directory("/abc"))
        self.assertEquals(mycwd, self.get_abs_directory(""))
        self.assertEquals("/ABC", self.get_abs_directory("/ABC/DEF"))
        try:
            self.get_abs_directory(None)
            self.assert_(False, "should raise exception")
        except TypeError:
            self.assert_(True, "woo hoo, exception")

    def get_abs_directory(self, path):
        if os.path.isdir(path):
            return path
        else:
            return os.path.split(os.path.abspath(path))[0]

if __name__ == '__main__':
    unittest.main()
于 2013-10-21T07:38:29.810 回答