断言 Python 字典中所有值的数据类型的更好方法

Better way to assert data type of all values in a Python dictionary

我正在构建一个单元测试 asserts/checks 如果字典中的所有值都具有相同的数据类型:float

Python 版本 3.7.4

假设我有四个不同的词典:

dictionary1: dict = {
    "key 1": 1.0,
}

dictionary2: dict = {
    "key 1": "1.0",
}

dictionary3: dict = {
    "key 1": 1.0,
    "key 2": 2.0,
    "key 3": 3.0
}

dictionary4: dict = {
    "key 1": "1",
    "key 2": "2",
    "key 3": 3
}

这样的单元测试用例:

class AssertTypeUnitTest(unittest.TestCase):

    def test_value_types(self):
        dictionary: dict = dictionary
        self.assertTrue(len(list(map(type, (dictionary[key] for key in dictionary)))) is 1 and
                            list(map(type, (dictionary[key] for key in dictionary)))[0] is float)


if __name__ == "__main__":
    unittest.main()

预期的结果是,如果字典中有一个值不是 float,它会抛出一个 AssertionError,即它会为 dictionary2 而不是 dictionary1.

现在虽然测试 确实适用于 1 个键值对 ,但在 dictionary3dictionary4 而无需添加另一个 for 循环?

for type in list(map(type, (dictionary[key] for key in dictionary))):
    self.assertTrue(type is float)

谢谢!

您可以将项目的类型转换为一个集合,并断言它等于一组 float:

self.assertSetEqual(set(map(type, dictionary.values())), {float})

让我们分解一下您编写的断言,以了解它实际上在做什么。

self.assertTrue(
    len(list(map(type, (dictionary[key] for key in dictionary)))) is 1  #
    and  #
    list(map(type, (dictionary[key] for key in dictionary)))[0] is float
)

你有两个条件必须为真才能通过断言。第一个是

len(list(map(type, (dictionary[key] for key in dictionary)))) is 1

这将创建一个列表,其中包含字典中每个值的类型,计算其长度并检查它是否为一个。好吧,这个列表将始终具有与您的字典相同数量的元素。检查这个长度是否等于一与你问的无关。

第二个条件是

list(map(type, (dictionary[key] for key in dictionary)))[0] is float

这将像以前一样创建一个列表,其中包含字典中每个值的类型,然后检查该列表的第一个元素是否为 float。这不一定意味着原始字典中的所有元素都是浮动的。

使用您已经编写的内容,简单的检查方法是获取类型列表,获取第一个元素,然后检查所有元素是否都等于第一个元素。

l = list(map(type, (dictionary[key] for key in dictionary)))
for t in l: 
    self.assertTrue(t==l[0])

但是有很多方法可以做测试。您还可以将此列表转换为集合并检查其长度是否等于 1(因为集合将只保留唯一值)

self.assertTrue(len(set(l)) == 1)
# or self.assertEqual(len(set(l)), 1)

我有一个类似的问题,但想使用 typing module of the python standard library. It uses the package pydantic 以更通用的方式解决它,这似乎是一个合理的依赖关系。这是我的方法。

import unittest
import typing
import pydantic

def check_type(obj, expected_type):

    class Model(pydantic.BaseModel):
        data: expected_type

    # convert ValidationError to TypeError if the obj does not match the expected type
    try:
        Model(data=obj)
    except pydantic.ValidationError as ve:
        raise TypeError(str(ve.errors()))

    return True  # allow constructs like assert check_type(x, List[float])

class TestCase1(unittest.TestCase):

    def test_check_type(self):

        obj1 = [3, 4, 5]
        obj2 = [3, 4, "x"]

        check_type(obj1, typing.List[int]) # pass silently

        with self.assertRaises(TypeError) as cm:
            check_type(obj2, typing.List[int])

        obj3 = {
                "key 1": 1.0,
                "key 2": 2.0,
                "key 3": 3.0
                }

        check_type(obj3, typing.Dict[str, pydantic.StrictFloat])

        obj3["key 3"] = "3.0"

        with self.assertRaises(TypeError) as cm:
            check_type(obj3, typing.Dict[str, pydantic.StrictFloat])

        # note that this passes:
        check_type(obj3, typing.Dict[str, float])


        # allow for multiple types:
        check_type(obj3, typing.Dict[str, typing.Union[int, float, str]])