当我使用切片语法 `mylist[:] = [mylist, mylist, ...]` 将带有自引用的列表分配给列表副本时发生了什么?

What is happening when I assign a list with self references to a list copy with the slice syntax `mylist[:] = [mylist, mylist, ...]`?

我正在查看 functools.lru_cache 的实现,偶然发现了这个片段:

root = []  # root of the circular doubly linked list
root[:] = [root, root, None, None]  # initialize by pointing to self

我熟悉循环和双向链表。我还知道 new_list = my_list[:] 创建了 my_list 的副本。 在查找切片分配或循环双向链表的其他实现时,我找不到有关此特定语法的任何更多信息。

问题:

  1. 这是怎么回事。
  2. 是否有不同的语法来实现相同的结果?
  3. some_list[:] = some_iterable(没有自我引用)是否有不同的常见用例?

root[:] = [root, root, None, None]

左手切片赋值只是说 root 的引用被重用来保存右边部分的内容。

所以 root 引用永远不会改变,是的,在列表中你可以引用自己(但不要尝试对那些进行递归展平 :)。在这种情况下,表示会显示 "recursion on list"。

>>> root
[<Recursion on list with id=48987464>,
 <Recursion on list with id=48987464>,
 None,
 None]

并打印它显示省略号:

>>> print(root)
[[...], [...], None, None]

请注意,您不需要为此分配切片。有一些简单的方法可以触发递归:

>>> root = []
>>> root.append(root)
>>> root
[<Recursion on list with id=51459656>]
>>> 

using append 不会像我们所知的那样改变引用,它只是改变列表,添加对自身的引用。也许更容易理解。

看看反汇编代码:

In [1]: def initializer():
   ...:     root = []  # root of the circular doubly linked list
   ...:     root[:] = [root, root, None, None]
   ...:     

In [2]: 

In [2]: import dis

In [3]: dis.dis(initializer)
  2           0 BUILD_LIST               0
              2 STORE_FAST               0 (root)

  3           4 LOAD_FAST                0 (root)
              6 LOAD_FAST                0 (root)
              8 LOAD_CONST               0 (None)
             10 LOAD_CONST               0 (None)
             12 BUILD_LIST               4
             14 LOAD_FAST                0 (root)
             16 LOAD_CONST               0 (None)
             18 LOAD_CONST               0 (None)
             20 BUILD_SLICE              2
             22 STORE_SUBSCR
             24 LOAD_CONST               0 (None)
             26 RETURN_VALUE

您要查找的是 STORE_SUBSCR 操作代码,它用于实现以下内容:

mplements TOS1[TOS] = TOS2

这是由于做文档和就地操作。如果你想知道什么是就地操作,文档是这样定义它的:

In-place operations are like binary operations, in that they remove TOS and TOS1, and push the result back on the stack, but the operation is done in-place when TOS1 supports it, and the resulting TOS may be (but does not have to be) the original TOS1.

这将验证源代码中的内联文档所说的内容:

initialize by pointing to self.

关于您的其他问题:

Is there a different syntax to achieve the same result?

是的,您可以像其他答案中提到的那样清除并使用 list.extend 属性设置列表项。或者一项一项分配可能哈哈

Is there a different common use case for some_list[:] = some_iterable (without the self reference)?

这是一个非常模糊的问题,因为它就是这样。以 Injective 方式分配项目,可以在不重新创建引用的情况下替换项目等

  1. 这种情况是怎么回事?

如果l是列表,l[:] = items调用l.__setitem__(slice(None), items)。此方法在清除给定的可迭代对象后将相应的项目分配给列表。

  1. 是否有不同的语法来实现相同的结果?

你可以

l.clear()
l.extend(items)
  1. some_list[:] = some_iterable (without the self reference)?
  2. 是否有不同的常见用例

理论上,您可以将任何可迭代对象放入列表中。