当我使用切片语法 `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 的副本。
在查找切片分配或循环双向链表的其他实现时,我找不到有关此特定语法的任何更多信息。
问题:
- 这是怎么回事。
- 是否有不同的语法来实现相同的结果?
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 方式分配项目,可以在不重新创建引用的情况下替换项目等
- 这种情况是怎么回事?
如果l
是列表,l[:] = items
调用l.__setitem__(slice(None), items)
。此方法在清除给定的可迭代对象后将相应的项目分配给列表。
- 是否有不同的语法来实现相同的结果?
你可以
l.clear()
l.extend(items)
some_list[:] =
some_iterable (without the self reference)?
是否有不同的常见用例
理论上,您可以将任何可迭代对象放入列表中。
我正在查看 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 的副本。
在查找切片分配或循环双向链表的其他实现时,我找不到有关此特定语法的任何更多信息。
问题:
- 这是怎么回事。
- 是否有不同的语法来实现相同的结果?
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 方式分配项目,可以在不重新创建引用的情况下替换项目等
- 这种情况是怎么回事?
如果l
是列表,l[:] = items
调用l.__setitem__(slice(None), items)
。此方法在清除给定的可迭代对象后将相应的项目分配给列表。
- 是否有不同的语法来实现相同的结果?
你可以
l.clear()
l.extend(items)
some_list[:] = some_iterable (without the self reference)?
是否有不同的常见用例
理论上,您可以将任何可迭代对象放入列表中。