【问题标题】:Python inserting multiple elements in one iteration in list comprehension conditionallyPython有条件地在列表理解的一次迭代中插入多个元素
【发布时间】:2019-03-10 05:35:26
【问题描述】:

假设我们必须在一次迭代中在列表推导中插入两个元素,但我们必须选择插入哪两个。我们如何进行这样的列表理解。

例如:- 假设我们有一个列表hostnames = ['aa', 'bb', 'dd', 'pp', 'bb', 'zz', 'hh']

现在我们要修改这个列表,使得以 p 开头的主机名应该有一个名为“_prd”的额外元素(例如“pp_prd”),并且类似地,对于以 h 开头且值为“_uat”的主机名来说,还有一个额外的元素。(例如'hh_uat')。

因此,上述列表所需的输出将是mod_hostnames = ['aa', 'bb', 'dd', 'pp_prd', 'pp', 'bb', 'zz', 'hh_uat','hh']

现在我想把它写成这样-

>>> fh=lambda x: x+'_uat'
>>> fp=lambda x:x+'_prd'
>>> fo=lambda x:x
>>> lis
['aa', 'bb', 'dd', 'pp', 'bb', 'zz', 'hh']
>>> hostnames = lis
>>> mod_hostnames = [f(host) for f in (fo,fp) if host[0]=='p' else f(host) for f in (fo,fh) if host[0]=='h' else host for host in hostnames]
  File "<stdin>", line 1
    mod_hostnames = [f(host) for f in (fo,fp) if host[0]=='p' else f(host) for f in (fo,fh) if host[0]=='h' else host for host in hostnames]
                                                                 ^
SyntaxError: invalid syntax

我们得到一个语法错误。我还知道,在列表推导中,第二个循环运行得更快(就像在 for 循环中一样),在我们的代码中,host in hostnames 运行得更快,而不是其他所需的方式。所以我尝试了这个:-

>>> mod_hostnames = [f(host) for host in hostnames for f in (fo,fp) if host[0]=='p' else for f in (fo,fh) if host[0]=='h' else for f in (fo)]  
File "<stdin>", line 1
    mod_hostnames = [f(host) for host in hostnames for f in (fo,fp) if host[0]=='p' else for f in (fo,fh) if host[0]=='h' else for f in (fo)]
                                                                                       ^
SyntaxError: invalid syntax

有什么建议吗?或者这在列表推导中是不可能的。
我知道这根本不可读,并且有更好的方法可以将其写出来。 (例如,使用 dict 'switch' 在 for 循环中的一个语句中写入此内容,或者在循环中使用旧的 if else 等)。

发布解决方案编辑:收到了精彩的回复。谢谢!有人可以解释为什么我发布的代码也是错误的吗?
我觉得这是因为当条件为真或假时评估的语句只是解析为一个空的 for 循环:for f in (fo,fp) if host[0]=='p' else for f in (fo,fh) if host[0]=='p' 程序进入一个空的 for 循环 for f in (fo,fp)。它是否正确?
我认为将我的错误理解逆向工程到 for 循环中会解决这个问题。

【问题讨论】:

    标签: python list for-loop if-statement list-comprehension


    【解决方案1】:

    实际上,最好从传统的for 循环开始,这样您就可以一窥其中的难点:

    mod_hostnames = []
    for name in hostnames:
        if name.startswith('p'):
            r = [name + '_prd', name]
        elif name.startswith('h'):
            r = [name + '_uat', name]
        else:
            r = [name]
        mod_hostnames.extend(r)
    
    assert mod_hostnames == ['aa', 'bb', 'dd', 'pp_prd', 'pp', 'bb', 'zz', 'hh_uat', 'hh']
    

    要创建理解列表,您需要使用 ... if ... elseif ... elif ... else 语句转换为单行语句。 ..

    您可以如下处理:

    mod_hostnames = []
    for name in hostnames:
        r = [name + '_prd', name] if name.startswith('p') else \
            ([name + '_uat', name] if name.startswith('h') else [name])
        mod_hostnames.extend(r)
    

    嗯,它变得有点棘手。

    要将此循环转换为理解列表,您需要遍历 r 值。您将在理解列表中获得理解列表。

    mod_hostnames = [item
                     for name in hostnames
                     for item in ([name + '_prd', name] if name.startswith('p') else
                                  ([name + '_uat', name] if name.startswith('h') else [name]))]
    

    哼!我认为保留一个理解列表太复杂了。谁会想要调试它?

    为了可维护性,我建议一个小功能的解决方案:

    def modify(name):
        if name.startswith('p'):
            return [name + '_prd', name]
        elif name.startswith('h'):
            return [name + '_uat', name]
        else:
            return [name]
    
    
    mod_hostnames = [item
                     for name in hostnames
                     for item in modify(name)]
    

    来了!

    【讨论】:

    • 感谢劳伦特!选择它作为正确答案,因为它更深入地介绍了创建列表推导的过程。我鼓励读者也查看@pault 的答案,因为它保留了问题的语法,因此对于寻求快速答案的读者来说可能更具可读性。
    【解决方案2】:

    列表理解ifelse ifelse一起使用并使用set

    mod = [i +'_prd' if i.startswith('p') else i + '_uat' if i.startswith('h') else i for i in hostnames]
    mod = sorted(list(set(mod) | set(hostnames)))
    # ['aa', 'bb', 'dd', 'hh', 'hh_uat', 'pp', 'pp_prd', 'zz']
    

    【讨论】:

    • @pault 固定需要额外的行来完成使用的set 可以不设置完成吗?
    • 这似乎符合OP的要求,假设元素的原始顺序无关紧要。有没有set的方法,如其他答案所示。
    • @pault 是的,我仔细检查过,即使输出显示之前或之后没有提到新项目,他说只是为了创建项目,希望这有效
    • 是的,顺序并不重要。
    【解决方案3】:

    你的想法没问题,但你的语法不正确。这是修改现有代码的一种方法:

    mod_hostnames = [
        f(host) for host in hostnames 
        for f in (
            (fo,fp) if host.startswith('p') else 
            (fo,fh) if host.startswith('h') else 
            (fo,)
        )
    ]
    print(mod_hostnames)
    #['aa', 'bb', 'dd', 'pp', 'pp_prd', 'bb', 'zz', 'hh', 'hh_uat']
    

    包裹if/else 以用括号修改f 的可迭代对象,并且您还需要(fo,) 中的尾随逗号使其成为一个元组。

    您也可以使用str.startswith,而不是索引字符串中的第一个字符。

    在任何情况下,传统的循环在这里都很好,并且可能更适合可读性/易于理解:

    mod_hostnames = []
    for host in hostnames:
        mod_hostnames.append(fo(host))
        if host.startswith('p'):
            mod_hostnames.append(fp(host))
        elif host.startswith('h'):
            mod_hostnames.append(fh(host))
    

    【讨论】:

    • 非常简洁的解决方案!我实际上首先从这个答案而不是选择的答案中理解了解决方案,因为它保持了问题的语法并且一目了然。 pault 我希望我选择 Laurent 的答案是正确的,因为它更深入(并且也消除了 lambda 函数!)。如果评论看起来杂乱或不必要,欢迎模组修改或删除,我只是觉得 pault 应该得到解释,因为他首先发布了。
    【解决方案4】:

    首先,您需要编写一个始终返回元组的转换函数,必要时使用单例元组:

    def transform(name):
        if name.startswith("p"):
            return (name + "_prd", name)
        if name.startswith("h"):
            return (name + "_uat", name)
        return (name,) # Singleton tuple.
    

    那么你可以这样做:

    import itertools
    mod_hostnames = list(itertools.chain.from_iterable(
        transform(name) for name in hostnames))
    print(mod_hostnames)
    

    itertools.chain.from_iterable 本质上是一个扁平化操作,它连接所有中间元组,而外部的list 将该生成器的输出转换为实际列表。

    但是,在这种情况下,使用传统的 for 循环的更简单的方法更有意义。

    【讨论】:

    • 感谢您提供替代解决方案并公开一个可能在其他情况下使其他人受益的模块。我在仔细阅读堆栈溢出以解决这个问题时确实遇到了这个问题,但觉得在问题中包含这个会使其太长和令人困惑。再次感谢!
    猜你喜欢
    • 1970-01-01
    • 2017-11-29
    • 1970-01-01
    • 1970-01-01
    • 2021-09-24
    • 1970-01-01
    • 2013-05-11
    • 2014-03-12
    • 2021-06-27
    相关资源
    最近更新 更多