一种在不进行类型条件判断的情况下遍历列表或字典值的方法
我在遍历一个来自第三方的JSON数据结构,代码对它所知甚少,但需要对其进行统计等信息的收集。我正在读取该数据结构中的一个元素,它可能属于多种类型中的任意一种,并试图尽量用同一份代码来处理,以降低代码的复杂性与重复性。
从这个JSON中取出的变量可能是一个列表或一个字典,我希望能够调用一个单一的函数,对两者中的所有值执行某些操作。
因此:
def do_stuff_to_values(obj):
for v in obj.values():
# do stuff with v
但如果按这种方式实现,它就只对字典起作用,因为列表没有 values() 方法。
我可以使用 isinstance 来检查它是否是字典:
def do_stuff_to_values(obj):
for v in (obj.values() if isinstance(obj, Mapping) else obj):
# do stuff with v
有没有办法在Python中表达让字典和列表得到相同的处理(其实字符串、元组、数组、集合也一样)?
似乎所有内置的序列类型都应该有一个values的迭代器方法,为什么它们没有呢?还是我遗漏了什么?
这是我一直在测试的代码:
mydict = {'k1': 'dv1', 'k2': 'dv2'}
myarray = ['av1', 'av2']
def get_values(obj):
return [i for i in (obj.values() if isinstance(obj, Mapping) else obj)]
print(get_values(mydict))
print(get_values(myarray))
解决方案
结合从他人及自身学到的东西来回答我自己的问题。
首先,字典在Python中应被理解为映射(Mappings),与序列(Sequences)区分开来。尽管现在它们是有序的;从概念上讲,它们没有values(),相反它们表示的是一个从 "from" 和 "to" 部分之间的映射。这就是它们没有values() 属性的原因。
对Mappings和 Sequences采用相同处理的代码应谨慎使用,通常最好让函数对被操作的类型有明确的预期。
因为它们在概念上是分离的,所以没有内置的方式在不显式区分它们的情况下处理它们。
如果出于设计原因需要这样做,那么通常更倾向于使用显式条件,例如isinstance。
就我而言,我的代码正在对一个数据结构进行分析并收集信息,结果大概是这样的:
from collections.abc import Iterable, Mapping, Sequence
def walk_datastructure(obj, depth = 0):
nonlocal max_depth
max_depth = depth if depth > max_depth else max_depth
types_found_set.add(type(obj).__name__)
if not isinstance(obj, Iterable) or isinstance(obj, str):
process_value(obj)
else:
for ref, value in obj.items() if isinstance(obj, Mapping) else enumerate(obj):
process_object(obj, ref)
walk_datastructure(value, depth + 1)
确实可以创建让它们以相同方式对待的代码,但在有限的场景下才有意义。
from collections.abc import Iterable, Mapping, Sequence
from functools import singledispatch
@singledispatch
def object_itervalues(arg):
assert isinstance(arg, Iterable)
return arg
@object_itervalues.register
def _(arg: Mapping):
return arg.values()
def test(obj):
for i in object_itervalues(obj):
print(i)
test(object_itervalues([1,3,2])) # list argument
test(object_itervalues((1,3,2))) # tuple argument
test(object_itervalues({"A":1,"B":3,"C":2})) # dict argument
或者使用内置的条件表达式:
from collections.abc import Mapping
def object_itervalues(obj):
if isinstance(obj, Mapping):
return obj.values()
return obj
感谢所有回复者,特别是Daweo、dROOOze、deceze、Paulo Oliveira。