64

I know how to convert a dictionary into a list in Python, but somehow when I try to get, say, the sum of the resulting list, I get the error 'dict_values' object is not subscriptable. Also, I plan to sum only several items in the list.

dict = {A:1, B:2, C:3, D:4}
arr = dict.values()
the_sum = sum(arr[1:3])

Upon closer inspection, I noticed that when the resulting list is printed out, it always gives dict_values(......) as the output which I can't remove. How do I get around this?

0

6 Answers 6

90

In Python 3, dict.values() returns a dict_values object, which is a view object providing a dynamic representation of the dictionary's values. This differs from Python 2, where it returned a list. The dict_values object offers several advantages and characteristics such as the followings:

  1. Dynamic View: It reflects real-time changes to the dictionary without creating a separate copy of the values.

  2. Memory Efficiency: As a view, it doesn't create a new list of values, saving memory.

  3. No Indexing or Slicing: Unlike lists, dict_values objects don't support indexing or slicing operations.

While dict_values objects don't have specific optimizations for membership checking, the underlying PyDictValues structure in CPython contributes to efficient dictionary operations:

typedef struct {
    uint8_t size;
    uint8_t embedded : 1;
    uint8_t valid : 1;
    uint8_t capacity;
    PyObject *values[1];
} PyDictValues;

Key optimizations in the CPython implementation include:

  1. Embedded Values: For small dictionaries, values might be stored directly in the object structure, improving access speed and memory usage.

  2. Capacity Management: The capacity field allows for efficient resizing operations.

  3. Valid Flag: Quick state checking for certain operations or consistency checks.

  4. Size Limitation: The assertion assert(size < 256) suggests an upper limit on the number of embedded values, balancing between optimization and general use cases.

If you need list-like functionality, you can convert a dict_values object to a list:

d = {'a': 1, 'b': 2, 'c': 3}
values_list = list(d.values())
print(sum(values_list[1:]))

This conversion creates a new list object, allowing for indexing and slicing, but at the cost of additional memory usage and losing the dynamic view property.

Sign up to request clarification or add additional context in comments.

8 Comments

"a dict-value object which is a set-like object and uses a hash table for storing its items" - That's incorrect; see docs: "Values views are not treated as set-like since the entries are generally not unique."
@wjandrea Yeah, I guess keys would be more accurate, thanks for pointing that out.
Keys? No, what I was saying was that a dict_values object isn't set-like and doesn't use a hash table. It doesn't even have keys, so I'm not sure where you got that from.
@wjandrea Yes, Of course. I'm not sure either. Perhaps in previous versions that was the case? Eitherway, I updated the answer. Thanks for the comment.
Awesome, much improved! However, I'm not sure what you mean by "optimized for operations like membership checking". Since they're not set-like, I don't think they can do any better than O(n), just like a list. You might still be thinking of sets, which are O(1) on average due to the hash table.
|
12

By assigning dict.values() to a list you are not converting it to a list; you are just storing it as dict_values (may be an object). To convert it write value_list=list(dict.values()). Now even while printing the value_list you will get the list elements and not dict_values(......).

And as mentioned before don't use Python built-in names as your variable names; it may cause conflicts during execution and confusion while reading your code.

1 Comment

"it may cause conflicts during execution" - Not conflicts per se - the builtin name just won't be accessible if it's shadowed by a global or local.
2

Creating a new list using list() constructor, only to slice it is wasteful. Using islice from the built-in itertools module will be more efficient.

from itertools import islice
dct = {'A':1, 'B':2, 'C':3, 'D':4}
arr = dct.values()
the_sum = sum(islice(arr, 1, 3))
the_sum
# 5

The difference is efficiency is noticeable especially if the dictionary is very large but the slice is small relative to it. For example, for a dictionary of 10mil key-value pairs, if you're slicing 20 pairs and summing their values, islice is ~39400x faster than constructing a list and slicing it.

n = 10_000_000
dct = dict(zip(range(n), [0,1,2,3,4]*(n//5)))

%timeit sum(list(dct.values())[10:30])
# 120 ms ± 813 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
%timeit sum(islice(dct.values(), 10, 30))
# 3.04 µs ± 19.4 ns per loop (mean ± std. dev. of 7 runs, 100 loops each)

Even if the slice is large, islice is still faster (though not as dramatic as a small slice)

%timeit sum(list(dct.values())[1:n])
# 277 ms ± 2.24 ms per loop (mean ± std. dev. of 7 runs, 100 loops each)

%timeit sum(islice(dct.values(), 1, n))
# 181 ms ± 4.9 ms per loop (mean ± std. dev. of 7 runs, 100 loops each)

Comments

1

Yes, it did appear like a list on the older Python questions asked here. But as @Kasramvd said, assuming you are using python 3.X, dict.values is a dictionary view object. (Also, you definitely came up with this example hastily as you have four dictionary entries but want 10 list items, which is redundant.)

Comments

0

@mgilson is right. A dictionary , by its intrinsic nature, isn't ordered.

Still you can do this :

alpha = {"A":1,"B":2,"C":3,"D":4,"E":5}   # dictionary
my_list = []
for key,value in alpha.items() :
    my_list.append(value)

You can access your "values" from my_list, but it will not be in order.

2 Comments

Dictionary values are ordered in Python 3. docs.python.org/3/library/stdtypes.html#dict-views says "Keys and values are iterated over in insertion order."
@coppit Check the note; "Changed in version 3.7: Dictionary order is guaranteed to be insertion order."
0

Using list(dictObj.values()) to wrap a dict_values object is not always successful, especially if the resulting dict_values list contains different data.

It will throw the Error:

{TypeError} 'generator' object is not callable

If you receive this error try to generate a iterable list using a for comprehension:

[x for x in dictObj.values()]

It will do the job

2 Comments

Huh? [x for x in dictObj.values()] is 100% functionally equivalent to list(dictObj.values()). There's no way the behaviour would be different, so I suspect the problem you had is elsewhere in your code, and you fixed it by accident while refactoring.
Maybe. I did not invent this error. It truely happened to me when dealing with this Problem. the comprehension solution solved it for me ;)

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.