带有if else的Python嵌套列表推导式

4
我尝试使用列表推导式替换值列表中的多个可能字符串值。我有一个列名列表,这些列名来自于“cursor.description”。
['UNIX_Time', 'col1_MCA', 'col2_MCA', 'col3_MCA', 'col1_MCB', 'col2_MCB', 'col3_MCB']

I then have header_replace;

{'MCB': 'SourceA', 'MCA': 'SourceB'}

我希望替换列名中header_replace.keys()对应的字符串值为实际的值。

以下循环是必须要用到的:

headers = []
for header in cursor.description:
    replaced = False
    for key in header_replace.keys():
        if key in header[0]:
            headers.append(str.replace(header[0], key, header_replace[key]))
            replaced = True
            break

    if not replaced:
        headers.append(header[0])

这将给我正确的输出;

['UNIX_Time', 'col1_SourceA', 'col2_SourceA', 'col3_SourceA', 'col1_SourceB', 'col2_SourceB', 'col3_SourceB']

我尝试使用了这个列表推导式;
[str.replace(i[0],k,header_replace[k]) if k in i[0] else i[0] for k in header_replace.keys() for i in cursor.description]

但这意味着对于未匹配的键,项目会被复制,我会得到;
['UNIX_Time', 'col1_MCA', 'col2_MCA', 'col3_MCA', 'col1_SourceA', 'col2_SourceA', 'col3_SourceA', 
'UNIX_Time', 'col1_SourceB', 'col2_SourceB', 'col3_SourceB', 'col1_MCB', 'col2_MCB', 'col3_MCB']

如果我使用以下代码:
[str.replace(i[0],k,header_replace[k]) for k in header_replace.keys() for i in cursor.description if k in i[0]]

@Bakuriu 修正语法

我可以获得正确的替换,但是会失去任何不需要进行字符串替换的项目。

['col1_SourceA', 'col2_SourceA', 'col3_SourceA', 'col1_SourceB', 'col2_SourceB', 'col3_SourceB']

有没有类似Python的方法来解决这个问题,或者说我是过度使用列表推导式了?我确实发现它们很难读懂。

4
我建议您使用循环,避免使用列表推导式。我的经验法则是,如果您在推导式中做了多于一件事情,最好将其扩展为循环,并使用.append方法。 - sneeu
@HansZauber 是的,抱歉,我会使用它们来编写CSV文件的标题,使用csv.writer(),通常我只是执行writer.writerow([i[0] for i in cursor.description]) - Dave Anderson
嵌套列表推导式(或生成器表达式)确实很难正确理解和阅读 - 虽然不是“火箭科学难度”,但足以使代码比普通的for循环更难理解。简而言之:对于简单、直接的情况,列表推导式非常好用,而对于复杂的情况,for循环更好用。 - bruno desthuilliers
@tobias_k 如果没有某种形式的 if,每次迭代键时都会得到每个元素的副本。 - Dave Anderson
1
@sneeu:或者使用一个生成器,yield那些应该放入列表中的东西,甚至比创建一个空列表并向其中添加更简单。 - RemcoGerlich
显示剩余3条评论
3个回答

8
[str.replace(i[0],k,header_replace[k]) if k in i[0] for k in header_replace.keys() for i in cursor.description]

这是一个SyntaxError,因为if表达式必须包含else部分。你可能想表达的是:

[i[0].replace(k, header_replace[k]) for k in header_replace for i in cursor.description if k in i[0]]

使用if放在最后的方式。然而,我必须说,嵌套循环的列表推导通常不是最佳选择。我会使用扩展的for循环。实际上,我会改进它,移除replaced标志:

headers = []
for header in cursor.description:
    for key, repl in header_replace.items():
        if key in header[0]:
            headers.append(header[0].replace(key, repl))
            break
    else:
        headers.append(header[0])

for循环的else部分在迭代过程中未触发break时执行。


我不明白为什么您的代码中使用str.replace(string, substring, replacement)而不是 string.replace(substring, replacement)。字符串具有实例方法,因此应将它们视为这样,而不是类的静态方法。


谢谢,不知道 for 循环中的 else 可以非常有用。 _ 的作用是什么? - Dave Anderson
@DaveAnderson 我使用元组解包来避免一直使用 header[0]。但我刚刚改变了它,因为那只有在 cursor.description 是一对时才有效,但由于它可以有任意数量的元素,所以我决定撤销这个更改。符号 _ 没有任何特定的含义。它通常用于表示“我不打算使用的变量,但是是必需的”。这种约定可能来自 Haskell,其中它是一个特殊的模式,表示“任何值都匹配”。 - Bakuriu

1
如果您的数据与您描述的完全相同,则无需嵌套替换,只需简化为以下一行即可:
l = ['UNIX_Time', 'col1_MCA', 'col2_MCA', 'col3_MCA', 'col1_MCB', 'col2_MCB', 'col3_MCB']
[i.replace('_MC', '_Source')  for i in l]

>>> ['UNIX_Time',
>>>  'col1_SourceA',
>>>  'col2_SourceA',
>>>  'col3_SourceA',
>>>  'col1_SourceB',
>>>  'col2_SourceB',
>>>  'col3_SourceB']

不幸的是,常见的 MCSource 没有保证,它们可能是从 M00MFF 的任何内容,并且替换值几乎可以是任何内容。 - Dave Anderson
在这种情况下,我建议编写专门的字符串替换函数 M_replace(source_string)。 - aisbaa
在这种情况下,我会选择使用正则表达式而不是一个庞大的替换字典。 - Michael

0

我认为使用函数会更易读:

def repl(key):
    for k, v in header_replace.items():
        if k in key:
            return key.replace(k, v)
    return key

print map(repl, names)

另一个(不太易读的)选项:

import re
rx = '|'.join(header_replace)
print [re.sub(rx, lambda m: header_replace[m.group(0)], name) for name in names]

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接