Python的字典视图对象是按值性质分配的例外

Are Python's dictionary view objects an exception to its assignment by value nature

根据栈溢出的这个问题,python中的赋值总是按值进行的,因此不能更改原始源。例如(来自同一个问题)

1
2
3
4
5
6
locs = [ [1], [2] ]
for loc in locs:
    loc = []

print locs
# prints => [ [1], [2] ]

但是,字典视图对象显示了相反的行为

1
2
3
4
5
6
7
bike = {"Manufacturer":"Honda","Model":"CBR","cc":250,"price":70,"mileage":74}

keys = bike.keys()
print(keys)

bike["tyres"] = 2
print(keys)

这是输出:

1
2
dict_keys(['mileage', 'price', 'Model', 'Manufacturer', 'cc'])
dict_keys(['cc', 'Manufacturer', 'tyres', 'mileage', 'Model', 'price'])

它们是否可以被视为按价值性质分配的一个例外?如果是的话,python3还有哪些例外?


不,这也不例外。没有为字典视图分配任何内容。视图明确记录为动态:

They provide a dynamic view on the dictionary’s entries, which means that when the dictionary changes, the view reflects these changes.

这是因为它们只存储对原始字典的引用,并以不同于字典API的方式直接访问键、值或(key, value)对。

如果需要,可以在对象上构建自己的视图,但请注意,这样的对象仍然需要对原始对象的引用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
from collections.abc import Sequence

class ListReversedView(Sequence):
    def __init__(self, lst):
        self._lst = lst
    def __getitem__(self, idx):
        if idx < 0:
            new = (-idx) - 1
        else:
            new = len(self) - idx - 1
            if new < 0:
                raise IndexError(new)
        return self._lst[new]
    def __len__(self):
        return len(self._lst)
    def __repr__(self):
        return f"[{', '.join(map(repr, self))}]"

上面的示例为您提供了不同的列表内容视图;对列表的更改反映在该对象提供的"视图"中。不需要对python的赋值模型做任何特殊的操作;赋值仍然只是对对象的引用,并且这个视图对象中的_lst属性也不例外:

1
2
3
4
5
6
7
>>> foo = ['spam', 'ham', 'eggs']
>>> view = ListReversedView(foo)
>>> view
['eggs', 'ham', 'spam']
>>> foo[-1] = 'bacon'
>>> view
['bacon', 'ham', 'spam']

循环回到您的列表循环;您仍然可以分配回列表对象本身;重新绑定名称可能不起作用,但重新绑定索引工作正常:

1
2
for index in range(len(locs)):
    locs[index] = []

总之,python对象都位于一个堆中,名称和属性只是引用这些对象。可以存在多个引用,并且每个这样的引用都将看到对对象所做的更改(如果允许)。分配只更改特定引用指向的内容。dict视图在这里也不例外,它们只是继续引用从中创建它们的字典。

您可能想了解一下Python模型;我强烈推荐NedBatchelder关于Python名称和值的事实和神话。