参数验证,Python中的最佳实践

时间:2013-10-21 05:55:58

标签: python

让我们举一个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)
  4. 此问题并非特定于文件IO,而是仅将FileIO用作示例

4 个答案:

答案 0 :(得分:6)

正如此处的文档所述,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(Look Before You Leap)风格中编码,这看起来像这样:

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)

答案 1 :(得分:3)

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

通常,类型检查有两个原因:确保您的功能实际完成,并避免因输出错误而导致难以调试的下游故障。

对于第一个问题,答案总是合适的 - EAFP是常规方法。而且你不担心糟糕的投入。

对于第二个......答案取决于您的正常使用情况,并且您担心输入/错误不良。当糟糕的输入总是产生异常时,EAFP仍然是合适的(并且它更容易,也更容易调试)(其中'坏输入'可能限于应用程序期望产生的坏输入类型)。但如果输入错误可能会产生有效输出,那么LYBL可能会让您的生活更轻松。

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

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

然而......让我们说你的应用程序使用了一些FP。并且它可能(假设你有一个错误!当然不是正常操作)你不小心叫方(1.43)。这将返回一个有效值 - 2.0449左右。你不会在这里得到例外,因此你的应用程序将很乐意接受2.0449并将其放入字典中。很久以后,你的应用程序会将这个值从字典中拉回来,将其用作列表的索引并且 - 崩溃。你将获得一个追溯,你将重新启动pdb,并意识到这根本没有帮助你,因为这个值是很久以前计算出来的,你不再有输入,或者知道这些数据是如何获得的那里。那些调试并不好玩。

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

答案 2 :(得分:2)

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

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

答案 3 :(得分:1)

代码确实"陷阱"如此测试代码所示的错误,引发了无异常

的异常
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()