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

问题描述 投票:0回答:5

我想在以下代码中对

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)

列表理解如下:

L_unique = [ item for item in L if item not in ???self??? ]

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

python list-comprehension
5个回答
13
投票

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

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

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

import gc L = [1,2,5,2,1,1,3,4] L_unique = [ item 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)

输出(
在线尝试!

): [1, 2, 5, 3, 4]

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

对于与您要求的样式完全相同的替代方案,仅替换您的

???self???

,请参阅 Mechanic Pig 的更新答案。

    


12
投票
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

对于您提供的示例,您可以使用 
list(dict.fromkeys(L))

在 Python 3.7+ 中获得相同的结果。这里我使用

dict
而不是
set
,因为
dict
可以保留插入顺序:
>>> list(dict.fromkeys(L))
[1, 2, 5, 3, 4]

根据@KellyBundy,我目前找到的方法是使用
gc.get_objects

,但是这个操作非常昂贵(因为它收集了超过1000个对象)并且我无法确定其准确性:

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

通过缓存降低操作成本:

>>> 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]



4
投票
L_unique

不会

存在
,但您可以使用set理解。 L = [1,2,5,2,1,1,3,4] L_unique = {x for x in L}

如果您希望将其他功能应用于 
x

,这是灵活的,但在这种简单的形式中,您最好只使用:

L = [1,2,5,2,1,1,3,4]
L_unique = set(L)

如果需要,可以将 
set

转换回

list
L = [1,2,5,2,1,1,3,4]
L_unique = list(set(L))

与原始列表相比,使用集合可能会改变元素的顺序。


1
投票

gc.get_objects(生成=无)

返回收集器跟踪的所有对象的列表,不包括 列表返回。如果 Generation 不是 None,则仅返回对象 被那一代的收藏家追踪。

3.8版本更改:新生成参数。

引发审核事件 gc.get_objects 并生成参数。

不使用
gc

一个简单的方法:

L = [1,2,5,2,1,1,3,4] L_unique = [] # This returns just a list of None _ = [L_unique.append(i) for i in L if i not in L_unique] L_unique

输出:

[1, 2, 3, 4, 5]

或者你可以使用这个:

L = [1,2,5,2,1,1,3,4] list(set(L))

输出:

[1, 2, 3, 4, 5]



0
投票
unique_everseen()

库中的

more_itertools
函数:
>>> from more_itertools import unique_everseen
>>> L = [1,2,5,2,1,1,3,4]
>>> list(unique_everseen(L))
[1, 2, 5, 3, 4]

这类似于其他几个人建议的
set()

方法,但保证保留顺序。

    

© www.soinside.com 2019 - 2024. All rights reserved.