1

我希望能够在混合类型的字典(包含 int、floats、strings、numpy.arrays)中进行比较。我的最小示例有一个字典列表,我想要一个函数(或生成器)来迭代该列表并挑选包含由 **kwargs 输入到该函数(或生成器)指定的键值对的元素(字典) )。

import re
list_of_dicts = [{'s1':'abcd', 's2':'ABC', 'i':42, 'f':4.2},
                 {'s2':'xyz', 'i':84, 'f':8.4}]

def find_list_element(**kwargs):
    for s in list_of_dicts:
        for criterion, criterion_val in kwargs.iteritems():
            if type(criterion_val) is str:
                if re.match(criterion_val, s.get(criterion, 'unlikely_return_val')):
                    yield s
                    continue
            if s.get(criterion, None) == criterion_val:
                yield s

print [a for a in find_list_element(i=41)]       # []
print [a for a in find_list_element(i=42)]       # [{'i': 42, 's2': 'ABC', 's1': 'abcd', 'f': 4.2}]
print [a for a in find_list_element(s1='xyz')]   # []
print [a for a in find_list_element(s2='xyz')]   # [{'i': 84, 's2': 'xyz', 'f': 8.4}]
print [a for a in find_list_element(s2='[a-z]')] # [{'i': 84, 's2': 'xyz', 'f': 8.4}]

我上面的两个问题是:

  1. 如果函数要求进行字符串比较,我想切换到正则表达式匹配(re.search 或 re.match),而不是纯字符串比较。在上面的代码中,这是通过辱骂的类型检查完成的,它看起来并不那么优雅。有没有更好的解决方案不涉及类型检查?或者,这是在 python 中允许类型检查的情况?

  2. **kwargs当然可以包含多个比较。目前我只能想到一个涉及一些标志的解决方案(found = False切换到 afound = True并在每次迭代结束时评估list_of_dicts)。在决定是否屈服之前,有没有一些聪明的方法来累积每个 s 的比较结果?

有没有办法让这整个 dicts 集合更漂亮?

PS:实际用例涉及获取的 MRI 数据集 (BRUKER) 的表示。数据集通过我已转换为表示所述扫描的对象的一部分的 dicts 的参数文件来表征。我正在收集这些数据集,并希望根据这些参数文件给出的某些标准进一步过滤它们。这些参数可以是字符串、数字和其他一些不太方便的类型。

更新和蒸馏答案

如果我想从@BrenBarn 和@srgerg 的输入中得出一个一致的答案,那就是这个

list_of_dicts = [{'s1':'abcd', 's2':'ABC', 'i':42, 'f':4.2},
                 {'s2':'xyz', 'i':84, 'f':8.4}]

 # just making up some comparison strategies
def regex_comp(a,b): return re.match(a,b)
def int_comp(a,b): return a==b
def float_comp(a,b): return round(a,-1) == round (b,-1)

pre_specified_comp_dict = {frozenset(['s1','s2']) : regex_comp,
                           frozenset(['i']): int_comp,
                           frozenset(['f']): float_comp}

def fle_new(**kwargs):
    chosen_comps={}
    for key in kwargs.keys():
        # remember, the keys here are frozensets
        cand_comp = [x for x in pre_specified_comp_dict if key in x]
        chosen_comps[key] = pre_specified_comp_dict[cand_comp[0]]

    matches = lambda d: all(k in d and chosen_comps[k](v, d[k])
                            for k, v in kwargs.items())

    return filter(matches, list_of_dicts)

现在唯一的挑战是想出一个无痛的创建策略pre_specified_comp_dict

4

3 回答 3

2

在这种情况下使用类型检查对我来说似乎没问题,因为您确实想要完全不同的行为,具体取决于类型。然而,你应该让你的类型检查更聪明一点。使用if isinstance(criterion_val, basestring)而不是直接检查str类型。这样,它仍然适用于 unicode 字符串。

避免类型检查的方法是预先指定每个字段的比较类型。查看您的示例数据,看起来每个字段始终具有一致的类型(例如,s1 始终是一个字符串)。如果是这种情况,您可以在字段名称和比较类型之间创建显式映射,例如:

regex_fields = ['s1', 's2']

然后在您的代码中,而不是类型检查,if criterion in regex_fields查看该字段是否应该与正则表达式进行比较。如果您有不止两种类型的比较,您可以使用 dict 将字段名称映射到某种 ID 进行比较操作。

这样做的好处是它更明确地对您的假设进行编码,因此如果进入一些奇怪的数据(例如,您期望数字的字符串),则会引发错误,而不是默默地应用适合类型的比较。它还保持字段和比较之间的关系“分离”,而不是将其埋在实际比较代码的中间。

如果您有大量字段以及针对它们的不同子集的许多不同比较操作,这可能特别值得做。在这种情况下,最好预先定义哪些比较适用于哪些字段名称(而不是哪些类型),而不是为每个比较即时决定。只要您始终根据字段名称知道要进行哪种类型的比较,这将使事情变得更清晰。但是,如果您需要添加新字段,它确实会增加维护开销,因此如果这只是针对私人受众的脚本,我可能不会这样做。

于 2013-04-08T05:34:34.227 回答
1

以下是我将如何实现您的find_list_element功能。它仍然使用 The Reviled Type Checking (TM),但它看起来更有说服力恕我直言:

def find_list_element(**kwargs):
    compare = lambda e, a: re.match(e, a) is not None if isinstance(e, str) else e == a
    matches = lambda d: all(k in d and compare(v, d[k]) for k, v in kwargs.items())
    return filter(matches, list_of_dicts)

(顺便说一下,我使用的是 Python 3,虽然代码在 Python 2.7 中工作,但应该使用basestring而不是strBrenBarn 已经指出的那样)。

请注意,我使用了 Python 的all函数来避免累积比较结果。

于 2013-04-08T05:58:19.260 回答
0

您可以在下面看到我的代码,它解决了多个比较的需要:

def find_dict(**kwargs):
    for data in lds: # lds is the same as list_of_dicts
        for key, val in kwargs.iteritems():
                 if not data.get(key, False) == val: return False
         else:
                 yield data

输出/输出:

find_dict(i=42, s1='abcd')
{'i': 42, 's2': 'ABC', 's1': 'abcd', 'f': 4.2}

我没有包含正则表达式比较的代码!

干杯!

于 2013-04-08T05:59:24.230 回答