【问题标题】:How can I do assignments in a list comprehension?如何在列表理解中做作业?
【发布时间】:2012-05-04 17:46:33
【问题描述】:

我想在列表推导中使用赋值运算符。我该怎么做?

以下代码是无效的语法。我的意思是将lst[0] 设置为空字符串'',如果它匹配pattern

[ lst[0] = '' for pattern in start_pattern if lst[0] == pattern ]

谢谢!

【问题讨论】:

  • 我认为你有这个从里到外......
  • 这个不是很清楚。这是一个直接的警告信号。

标签: python list-comprehension


【解决方案1】:

我可以建议你,使用类而不是使用变量来赋值。 当你使用一个类时,你可以简单地改变类的值,然后返回结果。

我添加了一个我做过的例子:

def foo():
class result:
    def __init__(self):
        self.key = 0
        self.value = 0
        
    def change(self, key, value):
        self.key= key
        self.value= value
        return key

result_object= result()
class_result= [result_object.change(key, value) for key, value in temp_dict.items() if value > result_object.value][-1]
return class_result

【讨论】:

    【解决方案2】:

    如上述答案中所述,列表理解功能中无法直接分配,但有一种可破解的方法可以使用 exec 方法来实现分配。

    不仅仅是assignments,我们可以在exec方法中传递任何python表达式来评估它。

    在你的情况下,它可以通过

    来实现
      [ exec(f"lst[0] = ''") for pattern in start_pattern if lst[0] == pattern ]
    

    如果我们有一个对象列表,并且我们想在特定条件下为对象的特定属性分配一个值,那么它可能是这样的:

    [ exec("p.age_status=child") for p in persons_obj_list if p.age <= 18 ]
    

    注意:这将更改列表中现有对象的状态,并且不会像在正常使用列表理解时返回的那样返回对象列表。

    【讨论】:

      【解决方案3】:

      Python 3.8 将引入Assignment Expressions

      这是一个新符号::=,它允许在(除其他外)理解中赋值。这个新的算子也被称为海象算子

      它将带来很多潜在的节省 w.r.t.计算/内存,从上面链接的 PEP 的以下 sn-p 可以看出(适用于 SO 的格式):

      语法和语义

      在可以使用任意 Python 表达式的大多数情况下, 可以出现命名表达式。这是NAME := expr 的形式,其中 expr 是除无括号外的任何有效 Python 表达式 元组,NAME 是一个标识符。

      这种命名表达式的值与合并的相同 表达式,具有额外的副作用,即目标是 分配该值:

      1. 处理匹配的正则表达式

        if (match := pattern.search(data)) is not None:
            # Do something with match
        
      2. 无法使用 2-arg iter() 重写的循环

        while chunk := file.read(8192):
            process(chunk)
        
      3. 重用计算成本很高的值

        [y := f(x), y**2, y**3]
        
      4. 在理解过滤子句及其输出之间共享子表达式

        filtered_data = [y for x in data if (y := f(x)) is not None]
        

      这已在最近发布的 alpha 版本中可用(不推荐用于生产系统!)。你可以找到the release schedule for Python 3.8 here

      【讨论】:

      • 很遗憾,您不能使用下标分配给表达式。 a[i] := 'foo' 会抛出异常:SyntaxError: cannot use assignment expressions with subscript
      • 在 python 中不是 None falsey 吗?即:is not None 是否必要,如果需要,何时?
      【解决方案4】:

      也许这不是您正在寻找的,但我相信值得展示这种情况。

      假设您有一个list 的字典,如下所示:

      >>> fruits
      [{'name': 'apple', 'quantity': 5}, {'name': 'banana', 'quantity': 4}]
      

      通过正常的列表推导,您可能会找到您正在寻找的对象:

      >>> [d for d in fruits if d['name'] == 'apple'] 
      [{'name': 'apple', 'quantity': 5}]
      

      因此,由于上面的 if 条件,您有一个包含单个元素的列表。

      因此,您可以索引唯一的元素,访问其中一个字典键并分配一个值:

      >>> [d for d in fruits if d['name'] == 'apple'][0]['quantity'] += 1
      

      结果如下:

      >>> fruits
      [{'name': 'apple', 'quantity': 6}, {'name': 'banana', 'quantity': 4}]
      

      【讨论】:

        【解决方案5】:

        如果你在问题中提到的是:

        [ lst[0] = '' for lst in listOfLists if lst[0] == pattern ]
        

        或用于模式列表

        [ lst[0] = '' for lst in listOfLists if lst[0] in patterns ]
        

        这其实很容易做到的

        [ [''] + lst[1:] for lst in listOfLists if lst[0] == pattern ]
        

        或再次查看模式列表

        [[''] + lst[1:] for lst in listOfLists if lst[0] in patterns ]
        

        【讨论】:

          【解决方案6】:

          您似乎将 Python 中的 list comprehensionlooping constructs 混淆了。

          列表推导式产生——一个列表!它不适合现有列表中的单个分配。 (虽然你可以折磨语法来做到这一点......)

          虽然不清楚您要从代码中做什么,但我认为它更类似于循环列表(流控制)与生成列表(列表理解)

          像这样遍历列表:

          for pattern in patterns:
             if lst[0] == pattern: lst[0]=''
          

          这是一种合理的方法,也是你在 C、Pascal 等语言中会做的事情。但你也可以只测试列表中的一个值并更改它:

          if lst[0] in patterns: lst[0] = ''
          

          或者,如果您不知道索引:

          i=lst.index[pattern]
          lst[i]=''
          

          或者,如果您有一个列表列表并且想要更改每个子列表的每个第一个元素:

          for i, sublst in enumerate(lst):
              if sublst[i][0] in patterns: sublist[i][0]=''
          

          等等等等等等。

          如果您想对列表的每个元素应用一些东西,那么您可以考虑使用列表推导式、映射或 Python 工具包中的许多其他工具之一。

          就个人而言,我通常更倾向于使用列表推导来创建列表:

           l=[[ x for x in range(5) ] for y in range(4)]  #init a list of lists...
          

          哪个更自然:

          l=[]
          for i in range(4):
             l.append([])
             for j in range(5):
                l[i].append(j)      
          

          但是要修改同一个列表,哪个更容易理解?

          这个:

          l=[['new value' if j==0 else l[i][j] for j in range(len(l[i]))] for i in range(len(l))]
          

          或者这个:

          for i,outter in enumerate(l):
              l[i][0]='new value'               
          

          YMMV

          Here 是一个很棒的教程。

          【讨论】:

          • 这应该写成if lst[0] in patterns: lst[0] = ''
          • @Tim:作为答案提交!如果没有显而易见的方法,我不敢相信我想出了其他三种方法...... ;-)
          • @Tim Pietzcker:那是我写它的方式,但我确实想表明(我的解释)他有这个由内而外......
          • @TimPietzcker:好吧,如果 OP 对此有赌注,可以这样写:lst[0]='' if lst[0]==[x for x in patterns][0] else lst[0]:D
          【解决方案7】:

          Python 语言对表达式和语句有不同的概念。

          赋值是一个语句,即使语法有时会诱使您认为它是一个表达式(例如,a=b=99 有效,但它是一种特殊的语法情况,并不意味着 b=99 是一个表达式,例如C)。

          列表推导是表达式,因为它们返回一个值,从某种意义上说,它们执行的循环是一个事件,重点是返回的列表。

          语句可以包含表达式,但表达式不能包含语句。

          也就是说,列表项分配在内部转换为方法调用(以允许创建类似列表的对象)并且方法调用是表达式。因此,您可以在技术上在表达式中使用列表项分配:

          [ lst.__setitem__(0, '') for pattern in start_pattern if lst[0] == pattern ]
          

          然而,这被认为是不好的,因为它会损害可读性,而阅读源代码的难易程度是 Python 语言的主要关注点。例如,您应该改写...

          for pattern in start_pattern:
              if lst[0] == pattern:
                  lst[0] = ''
          

          顺便说一句,感谢in 运算符,这相当于更具可读性

          if lst[0] in start_pattern:
              lst[0] = ''
          

          列表推导用于它们的返回值,它们在内部创建一个循环......如果你想要的是循环,那么只需编写一个循环......任何阅读代码试图理解它的作用的人都会感激很多(以及几周后包括你自己在内的任何人)。

          【讨论】:

            【解决方案8】:

            简而言之:你没有。列表推导用于生成列表,而不是修改现有列表。如果要修改列表,请使用 for 循环,因为这就是它们的用途。

            编写该代码的 Pythonic 方式类似于:

            for pattern in start_pattern:
                if lst[0] == pattern:
                    lst[0] = ''
                    #the following assumes that pattern will never be ''
                    #if that's possible, you can ignore this bit
                    break
            

            但是,如果您真的非常想在一个内部进行赋值,并且不介意每个处理过您的代码的 Python 程序员都讨厌它,那么您可以使用一些函数:

            • 如果你要分配的变量是全局的,那么你可以这样做

                  globals().update(var=value)
              
            • 如果要赋值的变量是可变序列或映射(如列表或字典)

                  list.__setitem__(index, value)
              

            【讨论】:

            • 中断总是有效的。即使数据中有'',也只会导致lst[0]被重新分配相同的值...
            • 除非 lst 是一种可变序列类型,其中元素分配已被重载以跟踪给定值被分配了多少次,或任何其他类型的非标准行为。
            • 嗯,好点子。不过,我希望这是非常罕见的情况:-)
            • 这可能比您想象的更常见,但比我的评论假设的要少。最近几次我创建了一个 __setitem__ 方法,他们没有做任何你不希望他们做的事情。但是,依靠他人的代码来表现得明智并不是一种非常安全的编码方式。
            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 2011-06-25
            • 1970-01-01
            • 1970-01-01
            • 2019-06-26
            • 1970-01-01
            • 1970-01-01
            • 2014-01-05
            相关资源
            最近更新 更多