Cyt*_*rak 5 python dictionary list circular-reference circular-list
因此,如果我有一个列表a并附a加到它,我将得到一个包含它自己的引用的列表。
>>> a = [1,2]
>>> a.append(a)
>>> a
[1, 2, [...]]
>>> a[-1][-1][-1]
[1, 2, [...]]
Run Code Online (Sandbox Code Playgroud)
这基本上会导致看似无限的递归。
不仅在列表中,在字典中也是如此:
>>> b = {'a':1,'b':2}
>>> b['c'] = b
>>> b
{'a': 1, 'b': 2, 'c': {...}}
Run Code Online (Sandbox Code Playgroud)
这可能是将列表存储在最后一个元素中并修改其他元素的好方法,但这不会起作用,因为在每个递归引用中都会看到更改。
我明白为什么会发生这种情况,即由于它们的可变性。但是,我对这种行为的实际用例很感兴趣。有人可以启发我吗?
Mar*_*ers 11
用例是 Python 是一种动态类型语言,其中任何东西都可以引用任何东西,包括它自己。
列表元素是对其他对象的引用,就像变量名和属性以及字典中的键和值一样。引用没有类型化,变量或列表不限于仅引用整数或浮点值。每个引用都可以引用任何有效的 Python 对象。(Python 也是强类型的,因为对象具有不会改变的特定类型;字符串仍然是字符串,列表仍然是列表)。
因此,由于 Python 是动态类型的,因此:
foo = []
# ...
foo = False
Run Code Online (Sandbox Code Playgroud)
是有效的,因为foo不限于特定类型的对象,Python 列表对象也是如此。
The moment your language allows this, you have to account for recursive structures, because containers are allowed to reference themselves, directly or indirectly. The list representation takes this into account by not blowing up when you do this and ask for a string representation. It is instead showing you a [...] entry when there is a circular reference. This happens not just for direct references either, you can create an indirect reference too:
>>> foo = []
>>> bar = []
>>> foo.append(bar)
>>> bar.append(foo)
>>> foo
[[[...]]]
Run Code Online (Sandbox Code Playgroud)
foo is the outermost [/]] pair and the [...] entry. bar is the [/] pair in the middle.
There are plenty of practical situations where you'd want a self-referencing (circular) structure. The built-in OrderedDict object uses a circular linked list to track item order, for example. This is not normally easily visible as there is a C-optimised version of the type, but we can force the Python interpreter to use the pure-Python version (you want to use a fresh interpreter, this is kind-of hackish):
>>> import sys
>>> class ImportFailedModule:
... def __getattr__(self, name):
... raise ImportError
...
>>> sys.modules["_collections"] = ImportFailedModule() # block the extension module from being loaded
>>> del sys.modules["collections"] # force a re-import
>>> from collections import OrderedDict
Run Code Online (Sandbox Code Playgroud)
now we have a pure-python version we can introspect:
>>> od = OrderedDict()
>>> vars(od)
{'_OrderedDict__hardroot': <collections._Link object at 0x10a854e00>, '_OrderedDict__root': <weakproxy at 0x10a861130 to _Link at 0x10a854e00>, '_OrderedDict__map': {}}
Run Code Online (Sandbox Code Playgroud)
Because this ordered dict is empty, the root references itself:
>>> od._OrderedDict__root.next is od._OrderedDict__root
True
Run Code Online (Sandbox Code Playgroud)
just like a list can reference itself. Add a key or two and the linked list grows, but remains linked to itself, eventually:
>>> od["foo"] = "bar"
>>> od._OrderedDict__root.next is od._OrderedDict__root
False
>>> od._OrderedDict__root.next.next is od._OrderedDict__root
True
>>> od["spam"] = 42
>>> od._OrderedDict__root.next.next is od._OrderedDict__root
False
>>> od._OrderedDict__root.next.next.next is od._OrderedDict__root
True
Run Code Online (Sandbox Code Playgroud)
The circular linked list makes it easy to alter the key ordering without having to rebuild the whole underlying hash table.
但是,我对这种行为的实际用例很感兴趣。有人可以启发我吗?
我不认为有很多有用的用例。允许这样做的原因是因为它可能有一些实际用例,并且禁止它会使这些容器的性能变差或增加它们的内存使用量。
Python 是动态类型的,您可以将任何 Python 对象添加到list. 这意味着人们需要采取特殊的预防措施来禁止向自身添加列表。这与(大多数)类型语言不同,因为类型系统,这不会发生。
因此,为了禁止这种递归数据结构,如果新添加的对象已经参与数据结构的更高层,则需要检查每个添加/插入/变异。这意味着在最坏的情况下,它必须检查新添加的元素是否位于它可以参与递归数据结构的任何地方。这里的问题是同一个列表可以在多个地方被引用,并且已经可以是多个数据结构的一部分,并且诸如列表/字典之类的数据结构可以(几乎)任意深度。这种检测要么很慢(例如线性搜索),要么会占用相当多的内存(查找)。所以简单地允许它更便宜。
Python 在打印时检测到这一点的原因是您不希望解释器进入无限循环,或者得到 RecursionError 或 StackOverflow。这就是为什么对于某些操作,例如打印(但也有深度复制),Python 会临时创建一个查找来检测这些递归数据结构并适当地处理它们。