【问题标题】:How can I transform nested for-loops with if-statements into a list comprehension?如何将带有 if 语句的嵌套 for 循环转换为列表理解?
【发布时间】:2020-12-02 13:20:47
【问题描述】:

我对列表推导完全陌生,我知道如何制作一个基本推导。但是,如何将这些带有 if 语句的嵌套 for 循环转换为列表推导式?

仅供参考:listDicts1listDicts2 是字典列表(不想提供它们,因为它们非常大)。

  for d in listDicts1:
        for d2 in listDicts2:
            if d['name'] in d2['text']:
                  d['person'].append(d2) 
        print(d)

我的尝试:

new = [d2 for d in listDicts1 for d in listDicts2 if d['name'] in d2['text']]

【问题讨论】:

  • 这对转换非常重要,不值得。您的原始代码简洁易读,而生成的列表理解则不然。
  • 这里的问题是列表推导用于创建新列表,而您想填充现有列表。如果您可以很好地创建一个新列表,那么您在底部的方式是完全可以的。不过,要填充 d['person'],您在顶部的第一种方式很好。
  • 好的!试图让我的代码“更快”,然后以原始方式保持它。干杯!
  • 最大的警告是您的代码修改 listDicts1 中的字典。这可能是一个不可接受的副作用,或者是预期的结果......

标签: python for-loop nested list-comprehension


【解决方案1】:

我倾向于同意@Aplet123 的评论,即 OP 的初始代码简洁易读。但是有一个重要的警告:它确实修改了listDicts1的字典,在我的书中这是一个很大的禁忌:想象一个毫无戒心的用户(你自己,在三个月内)一堆字典,将它们放在一个列表中并将其传递给您的函数,只是为了(后来,在调试和许多头疼的过程中)发现原始字典本身已被偷偷地修改为您的函数的副作用。 ..(我向你保证,这发生在最好的家庭中)。

因此,如果您希望保持初始 dicts 不变,这在更安全且恕我直言总是更可取的情况下,那么只要需要修改(当您的条件为 True 时),您就必须生成新的 dicts。

第 1 版:纯推导式(但也总是创建一个新的 dict):

listDicts3 = [{
    **d,
    **{'person': d.get('person', []) + [
        d2 for d2 in listDicts2 if d['name'] in d2['text']
    ]}} for d in listDicts1]

第 2 版,首选:仅在需要时创建新的 dicts(其他只是通过引用复制):

persons = [[d2 for d2 in listDicts2 if d['name'] in d2['text']] for d in listDicts1]
listDicts3 = [
    {**d, **dict(person=d.get('person', []) + p)} if p else d
    for d, p in zip(listDicts1, persons)
]

请注意,如果person 还不是d 的成员,则上述两个列表都以空列表开头。

测试示例:

listDicts1 = [dict(name='fred', person=[]), dict(name='paul', person=[])]
listDicts2 = [dict(text='paul ate an apple'), dict(text='anne reads a book')]

# ... (one of the code snippets above to make listDicts3)

listDicts3
# output:
[{'name': 'fred', 'person': []},
 {'name': 'paul', 'person': [{'text': 'paul ate an apple'}]}]

listDicts1
# out (shows it is unchanged)
[{'name': 'fred', 'person': []}, {'name': 'paul', 'person': []}]

# your code, with side-effect
for d in listDicts1:
    for d2 in listDicts2:
        if d['name'] in d2['text']:
              d['person'].append(d2) 
    print(d)
# out:
{'name': 'fred', 'person': []}
{'name': 'paul', 'person': [{'text': 'paul ate an apple'}]}

# HOWEVER:
listDicts1
# out:
[{'name': 'fred', 'person': []},
 {'name': 'paul', 'person': [{'text': 'paul ate an apple'}]}]

注意:您可以避免副作用并保持您的代码几乎原样:

for d in listDicts1:
    d = d.copy()
    for d2 in listDicts2:
        if d['name'] in d2['text']:
              d['person'].append(d2) 
    print(d)

【讨论】:

    【解决方案2】:

    我从您在这里构建循环的方式中推断出很多,如果您能提供一个最小的示例会更容易。

    listDicts1 = [{'name': 'A', 'person': []}, {'name': 'B', 'person': []}]
    listDicts2 = [{'text': ['A', 'B']}, {'text': ['B', 'C']}, {'text': ['C', 'A']}]
    [d1['person'].append(d2) for d1 in listDicts1 for d2 in listDicts2 if d1['name'] in 
    d2['text']]
    
    print(listDicts1)
    
    "[{'name': 'A', 'person': [{'text': ['A', 'B']}, {'text': ['C', 'A']}]}, {'name': 'B', 
     'person': [{'text': ['A', 'B']}, {'text': ['B', 'C']}]}]"
    

    这是否是一个好主意,还有待商榷,我唯一反对的是可读性。

    【讨论】:

    • 就像OP的代码一样,这也有修改listDicts1中的dicts的副作用。这几乎总是一个very.bad.idea
    • 可以想象,这似乎是代码问题的逻辑用法,而不是软件工程问题。正如您所详述的那样,使用适当的副本,这种理解是完全安全的。从逻辑上讲,这就是 OP 要求的答案。
    • 是的,但是没有副本是不安全的;当你看到一个小火,踩它。
    • 正如您在上面提到的,就地修改列表可能是理想的结果。不过,我一般不同意你的观点。
    • 两个答案都可以接受,但我从 Pierre D 那里学到的最多,谢谢你的努力!
    猜你喜欢
    • 1970-01-01
    • 2021-09-06
    • 1970-01-01
    • 2023-02-15
    • 2018-12-06
    • 1970-01-01
    • 2022-10-15
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多