What Are Dictionary View Objects

What are dictionary view objects?

Dictionary views are essentially what their name says: views are simply like a window on the keys and values (or items) of a dictionary. Here is an excerpt from the official documentation for Python 3:

>>> dishes = {'eggs': 2, 'sausage': 1, 'bacon': 1, 'spam': 500}
>>> keys = dishes.keys()
>>> values = dishes.values()

>>> # view objects are dynamic and reflect dict changes
>>> del dishes['eggs']
>>> keys # No eggs anymore!
dict_keys(['sausage', 'bacon', 'spam'])

>>> values # No eggs value (2) anymore!
dict_values([1, 1, 500])

(The Python 2 equivalent uses dishes.viewkeys() and dishes.viewvalues().)

This example shows the dynamic character of views: the keys view is not a copy of the keys at a given point in time, but rather a simple window that shows you the keys; if they are changed, then what you see through the window does change as well. This feature can be useful in some circumstances (for instance, one can work with a view on the keys in multiple parts of a program instead of recalculating the current list of keys each time they are needed)—note that if the dictionary keys are modified while iterating over the view, how the iterator should behave is not well defined, which can lead to errors.

One advantage is that looking at, say, the keys uses only a small and fixed amount of memory and requires a small and fixed amount of processor time, as there is no creation of a list of keys (Python 2, on the other hand, often unnecessarily creates a new list, as quoted by Rajendran T, which takes memory and time in an amount proportional to the length of the list). To continue the window analogy, if you want to see a landscape behind a wall, you simply make an opening in it (you build a window); copying the keys into a list would correspond to instead painting a copy of the landscape on your wall—the copy takes time, space, and does not update itself.

To summarize, views are simply… views (windows) on your dictionary, which show the contents of the dictionary even after it changes. They offer features that differ from those of lists: a list of keys contain a copy of the dictionary keys at a given point in time, while a view is dynamic and is much faster to obtain, as it does not have to copy any data (keys or values) in order to be created.

Python: Understanding dictionary view objects

Dict views store a reference to their parent dict, and they translate operations on the view to corresponding operations on the dict.

Iteration over a dict view is more efficient than building a list and iterating over that, because building a list takes time and memory that you don't have to spend with the view. The old way, Python would iterate over the dict's underlying storage to build a new list, and then you would iterate over the list. Iterating over a dict view uses an iterator that walks through the dict's underlying storage directly, skipping the unnecessary list step.

Dict views also support efficient containment tests and setlike intersection/difference/etc. operations, because they get to perform direct hash lookups on the underlying dict instead of iterating through a list and checking equality element by element.

If you want to see the concrete implementation used by CPython, you can take a look in the official repository, but this implementation is subject to change. It has changed, repeatedly.

Dictionary view objects vs sets

The implicit point of your comparison is that dict.keys() and set elements can't have duplicates. However, the set-like Dictionary view obtained from the keys still retains order, while the set does not.

Duplicate dictionary keys:

If a key occurs more than once, the last value for that key becomes the corresponding value in the new dictionary.

Duplicate set elements:

A set object is an unordered collection of distinct hashable objects.

From the above, sets are unordered while in the current Python version dictionaries maintain insertion order:

Changed in version 3.7: Dictionary order is guaranteed to be insertion order.

Because dictionaries have an insertion order they can be reversed, while such operation in a set would be meaningless:

Dictionaries and dictionary views are reversible.

Finally, a set can be altered, deleted and inserted from. A Dictionary view object only allows looking at contents, not changing them.

My question is, are these view objects entirely different to sets but happen to have similar properties? Or are they implemented using sets?

The documentation makes no claim about implementation details.

Any other major differences between the two?

The documentations state the difference between "Keys views" and "items view" or "values views".

Keys views are set-like (...)

If all values are hashable, so that (key, value) pairs are unique and hashable, then the items view is also set-like.

(Values views are not treated as set-like (...))

Equivalence of view objects of Python dict

keys and items views are set-like (or mostly set-like for items views with non-hashable values) - they behave like set objects in many ways, and particularly, it's easy to perform in tests on such views. That lets those views support an efficient == operation based on whether the two views contain the same elements.

For values views, there's no good way to implement such an == operation, so values views don't implement anything fancy for ==. They just inherit the default __eq__ implementation from object, so two values views will only be considered equal if they're the same object. Even for two views of the same dict, you'll only get True if they're actually the same view object:

In [2]: x = {}

In [3]: x.values() == x.values()
Out[3]: False

In [4]: v = x.values()

In [5]: v == v
Out[5]: True

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

No, this is not an exception. Nothing is assigned to the dictionary view. Views are explicitly documented as being dynamic:

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

That's because they only store a reference to the original dictionary and provide you with direct access to just the keys, or just the values, or to (key, value) pairs in ways that differ from the dictionary API.

You can build your own views onto objects if you wanted, but note that such an object still needs a reference to the original object(s):

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))}]"

The above example gives you a different view on list contents; changes to the list are reflected in the 'view' this object provides. Nothing special needs to be done to Python's assignment model; assignments are still just references to objects, and the _lst attribute in this view object is no exception:

>>> foo = ['spam', 'ham', 'eggs']
>>> view = ListReversedView(foo)
>>> view
['eggs', 'ham', 'spam']
>>> foo[-1] = 'bacon'
>>> view
['bacon', 'ham', 'spam']

Circling back to your list loop; you can still assign back to the the list object itself; rebinding names may not work, but rebinding the indices works just fine:

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

In summary, Python objects all live on a heap, and names and attributes are just referencing to those objects. Multiple references can exist, and each such a reference will see changes made to the object (if permitted). Assignment changes only what a specific reference points to. Dict views are no exception here, they just continue to reference the dictionary from which they were created.

You may want to read up on the Python model; I strongly recommend the Facts and myths about Python names and values article by Ned Batchelder.

Scope of dynamic view objects in Python 3.x

I would recommend not to use the term variable. Better use name that allows access to an object. For your example a and b are names for the objects dictionary and the keys of a dictionary. In Python 3 dict.keys() gives you a key-view object that reflects the changes in the underlying dictionary. Therefore, the key-view object keeps a reference to the dictionary.

So you don't delete the dictionary but rather the name pointing to it. Only if there are no more names (references to the dictionary) left, will the garbage collector remove the dictionary.

If you program in a structured way with functions that do not work on global objects, memory leaks are rather rare. In practice del is typically used sparingly.

Python 3.x's dictionary view objects and matplotlib

More of that error:

--> 512     return array(a, dtype, copy=False, order=order, subok=True)
513
514 def ascontiguousarray(a, dtype=None):

TypeError: float() argument must be a string or a number, not 'dict_values'

So the minimal example is:

np.array(d.keys(),dtype=float)

Without the dtype specification

In [16]: np.array(d.keys())
Out[16]: array(dict_keys([1, 3]), dtype=object)

The dict_keys is treated as an object. Usually you have to work at keeping np.array from treating an object as a list of numbers.

In [17]: np.fromiter(d.keys(),dtype=float)
Out[17]: array([ 1., 3.])

np.fromiter can handle d.keys(), treating it as a iterable. So there's some detail in how fromiter handles an iterable that is different from np.array.

A generator expression works the same way, e.g. (i for i in range(4)). fromiter can iterate through it, array either treats it as an object or raises an error.

If all the errors that the SO mentioned boiled down to np.array(...) handling a generator, then it might be possible to fix the behavior with one numpy change. Developers certainly wouldn't want to tweak every function and method that might accept a list. But it feels like a fundamental change that would have to be thoroughly tested. And even then it's likely to produce backward compatibility issues.

The accepted fix, for some time now, has been to pass your code through 2to3.

https://docs.python.org/2/library/2to3.html

for dictionaries:

Fixes dictionary iteration methods. dict.iteritems() is converted to dict.items(), dict.iterkeys() to dict.keys(), and dict.itervalues() to dict.values(). Similarly, dict.viewitems(), dict.viewkeys() and dict.viewvalues() are converted respectively to dict.items(), dict.keys() and dict.values(). It also wraps existing usages of dict.items(), dict.keys(), and dict.values() in a call to list.

Difference between iterate dictionary.items() vs list(dictionary.items())

The difference is that the list is unnecessary in most cases, and might even be harmful.

The .items() method returns a "view" into the data. As per the documentation:

The objects returned by dict.keys(), dict.values() and dict.items() are view objects. They provide a dynamic view on the dictionary’s entries, which means that when the dictionary changes, the view reflects these changes.

This is not the case when you wrap it in list, which preserves the keys/values/items as they appeared when the list was created.

In general, you should not use the version with list() if you are iterating with for. It's superfluous at best and adds unnecessary visual clutter to the code.

Edit: As per the other answer by Jan Stránský, list() also performs a full iteration pass on the data in order to construct the list. This is quite wasteful!

Python: can view objects show keys of zombie dictionaries?

The fundamental issue in your understanding here is that:

myDict = {}

Does nothing to the original dict, assignment never mutates, so the object that myDict was referring to is unmodified. Python objects will stay alive as long as something refers to them. CPython uses reference counting, and as an implementation detail, will reclaim objects immediately when their reference count reaches zero.

However, the dict_keys view object you created internally references the dictionary it is acting as a view over, so the original dictionary still has at least one reference to it, since the view object is alive. Note, though, the dict_keys API does not expose this reference, so if it is the only reference, you cannot really access your dict anymore (in any sane way).

Is it reliable behavior, that myKeys will always show the keys of the previous version of the dictionary?

You are misunderstanding the behavior. It is showing the keys of the same dictionary it has always been showing. There is no previous dictionary. The view is over the object, it doesn't care or know which variables happen to be referring to that object at any given time.

It's the same reason that if you do:

x = 'foo'
container = []
container.append(x)
x = 'bar'
print(container[0])

will still print "foo". Objects have no idea what names are referencing them at any given time, and should not care. And when one reference changes, the other references don't magically get updated.



Related Topics



Leave a reply



Submit