如何解决 if 条件中列表理解的当前状态?

Cla*_*dio 4 python list-comprehension

L我想在以下代码中翻转循环:

L = [1,2,5,2,1,1,3,4]
L_unique = []
for item in L:
    if item not in L_unique: 
        L_unique.append(item)
Run Code Online (Sandbox Code Playgroud)

列表理解如下:

L_unique = [ item for item in L if item not in ???self??? ]
Run Code Online (Sandbox Code Playgroud)

这在Python中可能吗?如果可能的话,怎样才能做到呢?

Mec*_*Pig 13

列表推导实际上创建了一个匿名函数然后调用它,但是构建的列表不会存储在局部变量字典中,而是存储在Python中维护的堆栈中,因此很少有Python级别的操作可以获得这个列表(这是gc一个疯狂但可行的选择。抱歉)对于我之前的夸张,使用的解决方案gc附在最后):

>>> [locals().copy() for i in range(3)]
[{'.0': <range_iterator at 0x207eeaca730>, 'i': 0},    # does not contain the built list
 {'.0': <range_iterator at 0x207eeaca730>, 'i': 1},
 {'.0': <range_iterator at 0x207eeaca730>, 'i': 2}]
>>> dis('[i for i in iterable]')
  1           0 LOAD_CONST               0 (<code object <listcomp> at 0x00000211FEAFD000, file "<dis>", line 1>)
              2 LOAD_CONST               1 ('<listcomp>')
              4 MAKE_FUNCTION            0
              6 LOAD_NAME                0 (iterable)
              8 GET_ITER
             10 CALL_FUNCTION            1
             12 RETURN_VALUE

Disassembly of <code object <listcomp> at 0x00000211FEAFD000, file "<dis>", line 1>:
  1           0 BUILD_LIST               0    # build an empty list and push it onto the stack
              2 LOAD_FAST                0 (.0)
        >>    4 FOR_ITER                 4 (to 14)
              6 STORE_FAST               1 (i)
              8 LOAD_FAST                1 (i)
             10 LIST_APPEND              2     # get the built list through stack and index
             12 JUMP_ABSOLUTE            2 (to 4)
        >>   14 RETURN_VALUE
Run Code Online (Sandbox Code Playgroud)

对于您提供的示例,您可以使用list(dict.fromkeys(L))Python 3.7+ 获得相同的结果。这里我用dict而不是set因为dict可以保留插入顺序:

>>> list(dict.fromkeys(L))
[1, 2, 5, 3, 4]
Run Code Online (Sandbox Code Playgroud)

根据 @KellyBundy ,我找到的当前方法是使用gc.get_objects,但此操作非常昂贵(因为它收集了超过 1000 个对象),并且我无法确定其准确性:

>>> [item for item in L if item not in gc.get_objects(0)[-1]]
[1, 2, 5, 3, 4]
Run Code Online (Sandbox Code Playgroud)

通过缓存降低操作成本:

>>> lst = None
>>> [item for item in L if item not in (lst := gc.get_objects(0)[-1] if lst is None else lst)]
[1, 2, 5, 3, 4]
Run Code Online (Sandbox Code Playgroud)

  • dict.fromkeys() 优于其他答案中建议的 set() 解决方案 (2认同)

Kel*_*ndy 12

这是可能的。这是一个可以做到这一点的黑客,但我不会在实践中使用它,因为它很讨厌并且依赖于可能改变的实现细节,而且我相信它也不是线程安全的。只是为了证明这是可能的。

你的“某处必须存在一个存储理解的当前状态的对象”基本上是正确的(尽管它不一定必须是Python列表对象,Python可以以其他方式存储元素并仅创建列表对象然后)。

我们可以在垃圾回收跟踪的对象中找到新的列表对象。在创建推导式列表之前收集列表的 ID ,然后再次查看并获取之前不存在的列表。

演示:

import gc

L = [1,2,5,2,1,1,3,4]

L_unique = [
    item

    # the hack to get self
    for ids in ({id(o) for o in gc.get_objects() if type(o) is list},)
    for self in (o for o in gc.get_objects() if type(o) is list and id(o) not in ids)

    for item in L
    if item not in self
]

print(L_unique)
Run Code Online (Sandbox Code Playgroud)

输出(在线尝试!):

[1, 2, 5, 3, 4]
Run Code Online (Sandbox Code Playgroud)

在从 Python 3.7 到 Python 3.11 的多个版本中进行了测试和工作。

对于具有您所要求的确切样式的替代方案,仅替换您的???self???,请参阅 Mechanic Pig 的更新答案。