断言 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 个键值对 ,但在 dictionary3
和 dictionary4
而无需添加另一个 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]])
我正在构建一个单元测试 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 个键值对 ,但在 dictionary3
和 dictionary4
而无需添加另一个 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]])