预计阅读本页时间:-
Dictionary Changes in Python 3.0
This chapter has so far focused on dictionary basics that span releases, but the dictionary’s functionality has mutated in Python 3.0. If you are using Python 2.X code, you may come across some dictionary tools that either behave differently or are missing altogether in 3.0. Moreover, 3.0 coders have access to additional dictionary tools not available in 2.X. Specifically, dictionaries in 3.0:
广告:个人专属 VPN,独立 IP,无限流量,多机房切换,还可以屏蔽广告和恶意软件,每月最低仅 5 美元
- Support a new dictionary comprehension expression, a close cousin to list and set comprehensions
- Return iterable views instead of lists for the methods D.keys, D.values, and D.items
- Require new coding styles for scanning by sorted keys, because of the prior point
- No longer support relative magnitude comparisons directly—compare manually instead
- No longer have the D.has_key method—the in membership test is used instead
Let’s take a look at what’s new in 3.0 dictionaries.
Dictionary comprehensions
As mentioned at the end of the prior section, dictionaries in 3.0 can also be created with dictionary comprehensions. Like the set comprehensions we met in Chapter 5, dictionary comprehensions are available only in 3.0 (not in 2.6). Like the longstanding list comprehensions we met briefly in Chapter 4 and earlier in this chapter, they run an implied loop, collecting the key/value results of expressions on each iteration and using them to fill out a new dictionary. A loop variable allows the comprehension to use loop iteration values along the way.
For example, a standard way to initialize a dictionary dynamically in both 2.6 and 3.0 is to zip together its keys and values and pass the result to the dict call. As we’ll learn in more detail in Chapter 13, the zip function is a way to construct a dictionary from key and value lists in a single call. If you cannot predict the set of keys and values in your code, you can always build them up as lists and zip them together:
>>> list(zip(['a', 'b', 'c'], [1, 2, 3])) # Zip together keys and values
[('a', 1), ('b', 2), ('c', 3)]
>>> D = dict(zip(['a', 'b', 'c'], [1, 2, 3])) # Make a dict from zip result
>>> D
{'a': 1, 'c': 3, 'b': 2}
In Python 3.0, you can achieve the same effect with a dictionary comprehension expression. The following builds a new dictionary with a key/value pair for every such pair in the zip result (it reads almost the same in Python, but with a bit more formality):
C:\misc> c:\python30\python # Use a dict comprehension
>>> D = {k: v for (k, v) in zip(['a', 'b', 'c'], [1, 2, 3])}
>>> D
{'a': 1, 'c': 3, 'b': 2}
Comprehensions actually require more code in this case, but they are also more general than this example implies—we can use them to map a single stream of values to dictionaries as well, and keys can be computed with expressions just like values:
>>> D = {x: x ** 2 for x in [1, 2, 3, 4]} # Or: range(1, 5)
>>> D
{1: 1, 2: 4, 3: 9, 4: 16}
>>> D = {c: c * 4 for c in 'SPAM'} # Loop over any iterable
>>> D
{'A': 'AAAA', 'P': 'PPPP', 'S': 'SSSS', 'M': 'MMMM'}
>>> D = {c.lower(): c + '!' for c in ['SPAM', 'EGGS', 'HAM']}
>>> D
{'eggs': 'EGGS!', 'ham': 'HAM!', 'spam': 'SPAM!'}
Dictionary comprehensions are also useful for initializing dictionaries from keys lists, in much the same way as the fromkeys method we met at the end of the preceding section:
>>> D = dict.fromkeys(['a', 'b', 'c'], 0) # Initialize dict from keys
>>> D
{'a': 0, 'c': 0, 'b': 0}
>>> D = {k:0 for k in ['a', 'b', 'c']} # Same, but with a comprehension
>>> D
{'a': 0, 'c': 0, 'b': 0}
>>> D = dict.fromkeys('spam') # Other iterators, default value
>>> D
{'a': None, 'p': None, 's': None, 'm': None}
>>> D = {k: None for k in 'spam'}
>>> D
{'a': None, 'p': None, 's': None, 'm': None}
Like related tools, dictionary comprehensions support additional syntax not shown here, including nested loops and if clauses. Unfortunately, to truly understand dictionary comprehensions, we need to also know more about iteration statements and concepts in Python, and we don’t yet have enough information to address that story well. We’ll learn much more about all flavors of comprehensions (list, set, and dictionary) in Chapters 14 and 20, so we’ll defer further details until later. We’ll also study the zip built-in we used in this section in more detail in Chapter 13, when we explore for loops.
Dictionary views
In 3.0 the dictionary keys, values, and items methods all return view objects, whereas in 2.6 they return actual result lists. View objects are iterables, which simply means objects that generate result items one at a time, instead of producing the result list all at once in memory. Besides being iterable, dictionary views also retain the original order of dictionary components, reflect future changes to the dictionary, and may support set operations. On the other hand, they are not lists, and they do not support operations like indexing or the list sort method; nor do they display their items when printed.
We’ll discuss the notion of iterables more formally in Chapter 14, but for our purposes here it’s enough to know that we have to run the results of these three methods through the list built-in if we want to apply list operations or display their values:
>>> D = dict(a=1, b=2, c=3)
>>> D
{'a': 1, 'c': 3, 'b': 2}
>>> K = D.keys() # Makes a view object in 3.0, not a list
>>> K
<dict_keys object at 0x026D83C0>
>>> list(K) # Force a real list in 3.0 if needed
['a', 'c', 'b']
>>> V = D.values() # Ditto for values and items views
>>> V
<dict_values object at 0x026D8260>
>>> list(V)
[1, 3, 2]
>>> list(D.items())
[('a', 1), ('c', 3), ('b', 2)]
>>> K[0] # List operations fail unless converted
TypeError: 'dict_keys' object does not support indexing
>>> list(K)[0]
'a'
Apart from when displaying results at the interactive prompt, you will probably rarely even notice this change, because looping constructs in Python automatically force iterable objects to produce one result on each iteration:
>>> for k in D.keys(): print(k) # Iterators used automatically in loops
...
a
c
b
In addition, 3.0 dictionaries still have iterators themselves, which return successive keys—as in 2.6, it’s still often not necessary to call keys directly:
>>> for key in D: print(key) # Still no need to call keys() to iterate
...
a
c
b
Unlike 2.X’s list results, though, dictionary views in 3.0 are not carved in stone when created—they dynamically reflect future changes made to the dictionary after the view object has been created:
>>> D = {'a':1, 'b':2, 'c':3}
>>> D
{'a': 1, 'c': 3, 'b': 2}
>>> K = D.keys()
>>> V = D.values()
>>> list(K) # Views maintain same order as dictionary
['a', 'c', 'b']
>>> list(V)
[1, 3, 2]
>>> del D['b'] # Change the dictionary in-place
>>> D
{'a': 1, 'c': 3}
>>> list(K) # Reflected in any current view objects
['a', 'c']
>>> list(V) # Not true in 2.X!
[1, 3]
Dictionary views and sets
Also unlike 2.X’s list results, 3.0’s view objects returned by the keys method are set-like and support common set operations such as intersection and union; values views are not, since they aren’t unique, but items results are if their (key, value) pairs are unique and hashable. Given that sets behave much like valueless dictionaries (and are even coded in curly braces like dictionaries in 3.0), this is a logical symmetry. Like dictionary keys, set items are unordered, unique, and immutable.
Here is what keys lists look like when used in set operations. In set operations, views may be mixed with other views, sets, and dictionaries (dictionaries are treated the same as their keys views in this context):
>>> K | {'x': 4} # Keys (and some items) views are set-like
{'a', 'x', 'c'}
>>> V & {'x': 4}
TypeError: unsupported operand type(s) for &: 'dict_values' and 'dict'
>>> V & {'x': 4}.values()
TypeError: unsupported operand type(s) for &: 'dict_values' and 'dict_values'
>>> D = {'a':1, 'b':2, 'c':3}
>>> D.keys() & D.keys() # Intersect keys views
{'a', 'c', 'b'}
>>> D.keys() & {'b'} # Intersect keys and set
{'b'}
>>> D.keys() & {'b': 1} # Intersect keys and dict
{'b'}
>>> D.keys() | {'b', 'c', 'd'} # Union keys and set
{'a', 'c', 'b', 'd'}
Dictionary items views are set-like too if they are hashable—that is, if they contain only immutable objects:
>>> D = {'a': 1}
>>> list(D.items()) # Items set-like if hashable
[('a', 1)]
>>> D.items() | D.keys() # Union view and view
{('a', 1), 'a'}
>>> D.items() | D # dict treated same as its keys
{('a', 1), 'a'}
>>> D.items() | {('c', 3), ('d', 4)} # Set of key/value pairs
{('a', 1), ('d', 4), ('c', 3)}
>>> dict(D.items() | {('c', 3), ('d', 4)}) # dict accepts iterable sets too
{'a': 1, 'c': 3, 'd': 4}
For more details on set operations in general, see Chapter 5. Now, let’s look at three other quick coding notes for 3.0 dictionaries.
Sorting dictionary keys
First of all, because keys does not return a list, the traditional coding pattern for scanning a dictionary by sorted keys in 2.X won’t work in 3.0. You must either convert to a list manually or use the sorted call introduced in Chapter 4 and earlier in this chapter on either a keys view or the dictionary itself:
>>> D = {'a':1, 'b':2, 'c':3}
>>> D
{'a': 1, 'c': 3, 'b': 2}
>>> Ks = D.keys() # Sorting a view object doesn't work!
>>> Ks.sort()
AttributeError: 'dict_keys' object has no attribute 'sort'
>>> Ks = list(Ks) # Force it to be a list and then sort
>>> Ks.sort()
>>> for k in Ks: print(k, D[k])
...
a 1
b 2
c 3
>>> D
{'a': 1, 'c': 3, 'b': 2}
>>> Ks = D.keys() # Or you can use sorted() on the keys
>>> for k in sorted(Ks): print(k, D[k]) # sorted() accepts any iterable
... # sorted() returns its result
a 1
b 2
c 3
>>> D
{'a': 1, 'c': 3, 'b': 2} # Better yet, sort the dict directly
>>> for k in sorted(D): print(k, D[k]) # dict iterators return keys
...
a 1
b 2
c 3
Dictionary magnitude comparisons no longer work
Secondly, while in Python 2.6 dictionaries may be compared for relative magnitude directly with <, >, and so on, in Python 3.0 this no longer works. However, it can be simulated by comparing sorted keys lists manually:
sorted(D1.items()) < sorted(D2.items()) # Like 2.6 D1 < D2
Dictionary equality tests still work in 3.0, though. Since we’ll revisit this in the next chapter in the context of comparisons at large, we’ll defer further details here.
The has_key method is dead: long live in!
Finally, the widely used dictionary has_key key presence test method is gone in 3.0. Instead, use the in membership expression, or a get with a default test (of these, in is generally preferred):
>>> D
{'a': 1, 'c': 3, 'b': 2}
>>> D.has_key('c') # 2.X only: True/False
AttributeError: 'dict' object has no attribute 'has_key'
>>> 'c' in D
True
>>> 'x' in D
False
>>> if 'c' in D: print('present', D['c']) # Preferred in 3.0
...
present 3
>>> print(D.get('c'))
3
>>> print(D.get('x'))
None
>>> if D.get('c') != None: print('present', D['c']) # Another option
...
present 3
If you work in 2.6 and care about 3.0 compatibility, note that the first two changes (comprehensions and views) can only be coded in 3.0, but the last three (sorted, manual comparisons, and in) can be coded in 2.6 today to ease 3.0 migration in the future.