Python Language Dictionary: Difference between revisions

From NovaOrdis Knowledge Base
Jump to navigation Jump to search
 
(84 intermediate revisions by the same user not shown)
Line 1: Line 1:
=Internal=
=Internal=
* [[Python_Language#Dictionary|Python Language]]
* [[Python_Language#Dictionary|Python Language]]
* [[Python_Language_List#Overview|List]]
=TODO=
<font color=darkkhaki>
* TO PROCESS [[PyOOP]] "Dictionaries", "Dictionary use cases", "Using defaultdict", "Counter"
* [[#Handle_Missing_Keys_with_setdefault.28.29_and_defaultdict.28.29|Handle Missing Keys with setdefault() and defaultdict()]]
* [[#Ordered_Dictionary|Ordered Dictionary]]
</font>
=Overview=
=Overview=
A dictionary is a mutable collection of key-value pairs. The pairs can be [[#Access_a_Dictionary|accessed]] and [[#Modify_a_Dictionary|modified]]. Each key is unique within the key set, and can be an instance of any immutable type: boolean, integer, float, tuple, string, etc. In other programming languages, the same data structure is referred to as "[[Bash_Arrays#Associative_Arrays|associative array]]" or "[[Hash_Table#Overview|hash tables]]" or "hash maps".
A dictionary is a mutable collection of key-value pairs. The pairs can be [[#Access_a_Dictionary|accessed]] and [[#Modify_a_Dictionary|modified]]. Each key is unique within the key set, and can be an instance of any immutable type: boolean, integer, float, tuple, string, etc., including <code>None</code>. There is a difference between a key that does not exist in the dictionary and a key that has been explicitly set to <code>None</code> - see [[#Access_with_.5B.5D_Syntax|Access with [] Syntax]] for more details. In other programming languages, the same data structure is referred to as "[[Bash_Arrays#Associative_Arrays|associative array]]" or "[[Hash_Table#Overview|hash tables]]" or "hash maps".
 
The same underlying dictionary data structure can be referred by multiple variable names, which can be assigned with <code>=</code>, and if the structure is changed, the change will be reflected if any of the variables is used for access. To avoid this, make a copy of the dictionary with <code>[[#Copy_a_Dictionary|copy()]]</code>.
 
=Dictionary <tt>type()</tt>=
 
The function <code>type()</code> applied to a dictionary returns:
<font size=-1>
<class 'dict'>
</font>
 
To check whether an instance is a dictionary:
<syntaxhighlight lang='py'>
i = ...
if type(i) is dict:
  ...
</syntaxhighlight >
For <code>dict</code> subclasses:
<syntaxhighlight lang='py'>
i = ...
if isinstance(i, dict):
  ...
</syntaxhighlight >
 
=Key Discussion=
=Key Discussion=
While the values of a dictionary can be any Python object, the keys have to be immutable object like scalar types (int, float, string) or tuples that contains immutable objects. They need to be [[Eq_()_and_hash_()_in_Python#Hashability|hashable]]. See [[#Dictionaries_and_the_hash_()_Function|Dictionaries and the <tt>_hash_()</tt> Function]] below.


The keys <code>1</code> and <code>True</code> are equivalent. <font color=darkkhaki>Why?</font>
The keys <code>1</code> and <code>True</code> are equivalent. <font color=darkkhaki>Why?</font>
==<tt>None</tt> as Key==
<code>None</code> is a valid key:
<syntaxhighlight lang='py'>
d = {}
d[None] = 10
d['A'] = 20
assert len(d.keys()) == 2
assert d[None] == 10
assert d['A'] == 20
</syntaxhighlight>
==Dictionaries and the <tt>__hash__()</tt> Function==
{{Internal|Eq_()_and_hash_()_in_Python#Dictionaries_and_the_hash_.28.29_Function|Dictionaries and the <code>__hash__()</code> Function}}
=Dictionaries and the <tt>==</tt> Operator=
<font color=darkkhaki>It would be nice if <code>==</code> recursively deep-compares maps, and it seems to be working, Research this and come up with a definitive conclusion.</font>


=Create a Dictionary=
=Create a Dictionary=
Line 18: Line 70:
</syntaxhighlight>
</syntaxhighlight>
It is good form to insert a space after <code>:</code>. A comma is tolerated after the last pair.
It is good form to insert a space after <code>:</code>. A comma is tolerated after the last pair.
==Converting Other Data Structures to a Dictionary==
Any two-value sequences can be converted into a dictionary using the <code>dict()</code> function.
Examples:
<syntaxhighlight lang='python'>
# list of lists
l = [['a', 'b'], ['c', 'd'], ['e', 'f']]
assert dict(l) == {'a': 'b', 'c': 'd', 'e': 'f'}
# list of tuples
l = [('a', 'b'), ('c', 'd'), ('e', 'f')]
assert dict(l) == {'a': 'b', 'c': 'd', 'e': 'f'}
# tuple of two-item lists
l = (['a', 'b'], ['c', 'd'], ['e', 'f'])
assert dict(l) == {'a': 'b', 'c': 'd', 'e': 'f'}
# list of two-character strings
l = ['ab', 'cd', 'ef']
assert dict(l) == {'a': 'b', 'c': 'd', 'e': 'f'}
# tuple of two-character strings
l = ('ab', 'cd', 'ef')
assert  dict(l) == {'a': 'b', 'c': 'd', 'e': 'f'}
</syntaxhighlight>
==From a Key Sequence and a Value Sequence with <tt>zip()</tt>==
The sequences must not be of the same type, and neither have the same length:
<syntaxhighlight lang='py'>
l = ['a', 'b', 'c', 'd']
t = (10, 22, 77, 99, 1010)
assert  dict(zip(l, t)) == {'a':10, 'b':22, 'c':77, 'd':99}
</syntaxhighlight>
Also see: {{Internal|Python_zip()|<tt>zip()</tt>}}
==With Comprehensions==
{{Internal|Python_Comprehensions#Dictionary_Comprehensions|Dictionary Comprehensions}}


=Access a Dictionary=
=Access a Dictionary=
==Test for Empty Dictionary==
Empty dictionaries evaluate to <code>False</code>:
<syntaxhighlight lang='py'>
d1 = {}
if not d1:
    print("dictionary is empty")
</syntaxhighlight>
==Size of a Dictionary==
The number of key is given by the <code>len()</code> function:
<syntaxhighlight lang='py'>
d = {...}
print(len(d))
</syntaxhighlight>
==Access Individual Elements==
==Access Individual Elements==
Individual dictionary elements can be accessed with the <code>[[#Access_with_.5B.5D_Syntax|[]]]</code> syntax or with the <code>[[#get.28.29_Function|get()]]</code> function.  
Individual dictionary elements can be accessed with the <code>[[#Access_with_.5B.5D_Syntax|[]]]</code> syntax or with the <code>[[#get.28.29_Function|get()]]</code> function.  
===Access with <tt>[]</tt> Syntax===
===Access with <tt>[]</tt> Syntax===
The <code>[]</code> syntax can only be used with keys that exist in the dictionary:
The <code>[]</code> syntax can only be used with keys that exist in the dictionary:
Line 30: Line 135:
print(d['a'])
print(d['a'])
</syntaxhighlight>
</syntaxhighlight>
An attempt to access an inexistent key will throw a <code>KeyError</code> exception. To avoid the exception, use <code>[[#get.28.29_Function|get()]]</code> instead, or [[#Test_the_Existence_of_a_Key|test the existence of the key]] first.
An attempt to access an inexistent key will throw a <code>KeyError</code> exception. To avoid the exception, use <code>[[#get.28.29_Function|get()]]</code> instead, or [[#Test_the_Existence_of_a_Key|test the existence of the key]] first.  
 
However, a key can be explicitly set to <code>None</code>, and in this case, square bracket access to the key will return <code>None</code>:
<syntaxhighlight lang='py'>
d = {'a': None}
assert d['a'] is None
</syntaxhighlight>


===<tt>get()</tt> Function===
===<tt>get()</tt> Function===
The <code>get()</code> will return the associated value, or <code>None</code> if the key does not exist.
<syntaxhighlight lang='py'>
<syntaxhighlight lang='py'>
d = {'a': 'b'}
d = {'a': 'b'}
print(d.get('a')) # will display 'a'
assert d.get('a') == 'b'
print(d.get('no-such-key')) # will display None
assert d.get('no-such-key') is None
</syntaxhighlight>
The <code>get()</code> function allows for a second argument which will be returned instead of <code>None</code> in case the key does not exist:
<syntaxhighlight lang='py'>
assert d.get('no-such-key', 'alternative') == 'alternative'
</syntaxhighlight>
</syntaxhighlight>
===Safely Navigate a Complex Data Structure===
Suggestions on how to safely recursively navigate a complex data structure:
{{Internal|Python Safely Navigate a Complex Data Structure#Overview|Safely Navigate a Complex Data Structure}}
==Test the Existence of a Key==
==Test the Existence of a Key==


Line 44: Line 165:
if 'some-key' in d:
if 'some-key' in d:
   print("key exists")
   print("key exists")
</syntaxhighlight>
The non-existence is tested with this idiom:
<syntaxhighlight lang='py'>
if 'some-key' not in d:
  print("key does not exist")
</syntaxhighlight>
</syntaxhighlight>


Line 52: Line 179:
</syntaxhighlight>
</syntaxhighlight>


==Get All Keys==
==<span id='Get_All_Keys'></span>Get All Keys with <tt>keys()</tt> Function==
==Get All Values==
<syntaxhighlight lang='py'>
d = {'a': 'b', 'c': 'd'}
print(d.keys())
</syntaxhighlight>
In Python 3, the <code>keys()</code> will return a <code>dict_keys()</code>, which is an iterable view of keys. This is useful with large dictionaries because the runtime does not use the time and the memory to create and store a list that might not be used. In case you need a list, use <code>list()</code> to wrap the result of <code>keys()</code>.
<syntaxhighlight lang='py'>
list(d.keys())
</syntaxhighlight>
 
==<span id='Get_All_Values'></span>Get All Values with <tt>values()</tt> Function==
<syntaxhighlight lang='py'>
d = {'a': 'b', 'c': 'd'}
print(d.values())
</syntaxhighlight>
<font color=darkkhaki>In Python 3, the <code>values()</code> will return a <code>dict_values()</code>, which is an iterable view of values. This is useful with large dictionaries because the runtime does not use the time and the memory to create and store a list that might not be used. In case you need a list, use <code>list()</code> to wrap the result of <code>values()</code>.</font>
<syntaxhighlight lang='py'>
list(d.values())
</syntaxhighlight>
 
==<span id='Get_All_Key-Value_Pairs'></span>Get All Key-Value Pairs with <code>items()</code> Function==
<syntaxhighlight lang='py'>
d = {'a': 'b', 'c': 'd'}
print(d.items())
</syntaxhighlight>
In Python 3, the <code>items()</code> will return a <code>dict_items()</code>, which is an iterable view of items. This is useful with large dictionaries because the runtime does not use the time and the memory to create and store a list that might not be used. Use use <code>list()</code> to wrap the result of <code>items()</code>. The list contains tuples of key and values.
<syntaxhighlight lang='py'>
for i in list(d.items()):
    print('key:', i[0])
    print('value:', i[1])
</syntaxhighlight>
Equivalent:
<syntaxhighlight lang='py'>
for (k, v) in list(d.items()):
    print('key:', k)
    print('value:', v)
</syntaxhighlight>
 
==Iterate over a Dictionary==
Use a [[Python_Language#for|<code>for</code> loop]]:
===Iterate over Keys===
Use the dictionary itself as argument of <code>in</code>. This is equivalent with iterating over the results of the [[#Get_All_Keys_with_keys.28.29_Function|<code>keys()</code> function]].
<syntaxhighlight lang='py'>
d = {'a':'A', 'b':'B', 'c':'C'}
for k in d:
    print(k)
</syntaxhighlight>
This will display:
<syntaxhighlight lang='py'>
a
b
c
</syntaxhighlight>
 
===Iterate over Values===
Apply the <code>in</code> operator to the result of the [[#Get_All_Values_with_values.28.29_Function|<code>values()</code> function]]:
<syntaxhighlight lang='py'>
d = {'a':'A', 'b':'B', 'c':'C'}
for v in d.values():
    print(v)
</syntaxhighlight>
This will display:
<syntaxhighlight lang='py'>
A
B
C
</syntaxhighlight>
 
===Iterate over Both Keys and Values===
Apply the <code>in</code> operator to the result of the [[#Get_All_Key-Value_Pairs_with_items.28.29_Function|<code>items()</code> function]]. Each element is a tuple containing the key and the corresponding value:
<syntaxhighlight lang='py'>
d = {'a':'A', 'b':'B', 'c':'C'}
for kvt in d.items():
    print(kvt)
</syntaxhighlight>
This will display:
<syntaxhighlight lang='py'>
('a', 'A')
('b', 'B')
('c', 'C')
</syntaxhighlight>
Variables corresponding to the elements of the tuple can be assigned in one step, operation known as [[Python_Language_Tuple#Tuple_Unpacking|tuple unpacking]], so the following syntax where we assign the key and the value to their corresponding variable is valid:
<syntaxhighlight lang='py'>
d = {'a':'A', 'b':'B', 'c':'C'}
for k, v in d.items():
    print('key:', k, 'value:', v)
</syntaxhighlight>
This will display:
<syntaxhighlight lang='py'>
key: a value: A
key: b value: B
key: c value: C
</syntaxhighlight>


=Modify a Dictionary=
=Modify a Dictionary=
==Add an Element==
Individual elements can be added with the <code>[]</code> syntax:
<syntaxhighlight lang='py'>
d = {}
d['a'] = 'b'
assert d['a'] == 'b'
</syntaxhighlight>
===Handle Missing Keys with <tt>setdefault()</tt> and <tt>defaultdict()</tt>===
If the values are containers that need initialization when a key is first added to the dictionary, <code>setdefault()</code> is a dictionary method that set a value to the given default or returns the value if the key already exists:
<syntaxhighlight lang='py'>
d = {}
d.setdefault('a', []).append('b')
assert d['a'] == ['b']
</syntaxhighlight>
<font color=darkkhaki>TODO [[IPy]] Page 116.</font>
== Modify Individual Elements==
== Modify Individual Elements==
===Modification with <tt>[]</tt> Syntax===
===Modification with <tt>[]</tt> Syntax===
Line 62: Line 297:
d = {}
d = {}
d['a'] = 'b'
d['a'] = 'b'
print(d['a']) # will display "a"
assert d['a'] == 'b'
d['a'] = 'c'
d['a'] = 'c'
print(d['a']) # will display "c"
assert d['a'] == 'c'
</syntaxhighlight>
 
The syntax can be used with both literals and variables:
<syntaxhighlight lang='py'>
d = {}
d['a'] = 'b'
k = 'c'
d[k] = 'd'
assert d['c'] == 'd'
</syntaxhighlight>
</syntaxhighlight>
===<span id='Delete_Elements'></span>Delete Individual Element===
===<span id='Delete_Elements'></span>Delete Individual Element===
====With <tt>del</tt>====
<syntaxhighlight lang='py'>
<syntaxhighlight lang='py'>
d = {'a': 'b', 'c': 'd'}
d = {'a': 'b', 'c': 'd'}
Line 72: Line 318:
print(d) # will display {'c': 'd'}
print(d) # will display {'c': 'd'}
</syntaxhighlight>
</syntaxhighlight>
Note that if the key being deleted does not exist, the <code>[]</code> syntax will throw an <code>KeyError</code> exception.
Note that if the key being deleted does not exist, the <code>[]</code> syntax will throw an <code>KeyError</code> exception. The solution is to guard with an <code>if</code>:
<syntaxhighlight lang='py'>
if 'a' in d:
  del d['a']
</syntaxhighlight>
 
====With <tt>pop()</tt>====
<code>pop()</code> returns the value for the key provided as argument, and removes the key, if the key is in the dictionary. Throws KeyError if the key is not in the dictionary.
<syntaxhighlight lang='py'>
d = {'a': 'A'}
assert d.pop('a') == 'A'
assert len(d) == 0
</syntaxhighlight>
 
The missing key can be recovered from the <code>KeyError</code> instance. The name of the missing key, as string, is propagated as the exception message as the first element of the <code>args</code> tuple, and can be accessed as such:
<syntaxhighlight lang='py'>
try:
  ...
except KeyError as e:
  print(e.args[0])
</syntaxhighlight>
 
==Delete All Items==
==Delete All Items==
Use the <code>clear()</code> function:
Use the <code>clear()</code> function:
Line 81: Line 348:


==Combine Dictionaries==
==Combine Dictionaries==
Two dictionaries can be combined with <code>update()</code>:
<syntaxhighlight lang='py'>
d1 = {'a': 'b'}
d2 = {'c': 'd'}
d1.update(d2) # d1 will become {'a': 'b', 'c': 'd'}, d2 will remain unchanged
</syntaxhighlight>
If the same key exists in both dictionaries, the value associated with the key in the second dictionary will take precedence.
=Copy a Dictionary=
<syntaxhighlight lang='py'>
d1 = {'a': 'b'}
d2 =d1.copy()
d1['a'] = 'c'
print(d1['a']) # will display 'c'
print(d2['a']) # will display 'b'
</syntaxhighlight>
=Recursively Merge Dictionaries=
Use <code>update()</code>. This only works on shallow trees, it fails to do deep recursive merge. Any existing keys in the data passed to update will have their old values discarded.
<syntaxhighlight lang='py'>
dict1 = {
    'a': 'A',
}
dict2 = {
    'b': 'B',
    'c': ['C', 'D']
}
dict1.update(dict2)
assert dict1 == {
    'a': 'A',
    'b': 'B',
    'c': ['C', 'D']
}
</syntaxhighlight>
=Ordered Dictionary=
<font color=darkkhaki>TODO [[IPy]] Page 120.</font>
=Subclass a Dictionary=
<syntaxhighlight lang='py'>
class StorageClass(dict):
    def __init__(self, storage_class_config):
        super().__init__()
        name = None
        if storage_class_config:
            name = storage_class_config.get('name')
        if not name:
            raise ValueError("invalid 'config.csi_driver.storage_classes' element: missing 'name'")
        self['name'] = name
        self['volumeBindingMode'] = 'WaitForFirstConsumer'
        self['reclaimPolicy'] = 'Delete'
        self['allowVolumeExpansion'] = True
        self['parameters'] = {
            'type': 'gp3',
            'encrypted': 'true',
            'csi.storage.k8s.io/fstype': 'ext4',
            'throughput': '125',
            'iops': '3000'
        }
</syntaxhighlight>

Latest revision as of 17:17, 17 May 2024

Internal

TODO

Overview

A dictionary is a mutable collection of key-value pairs. The pairs can be accessed and modified. Each key is unique within the key set, and can be an instance of any immutable type: boolean, integer, float, tuple, string, etc., including None. There is a difference between a key that does not exist in the dictionary and a key that has been explicitly set to None - see Access with [] Syntax for more details. In other programming languages, the same data structure is referred to as "associative array" or "hash tables" or "hash maps".

The same underlying dictionary data structure can be referred by multiple variable names, which can be assigned with =, and if the structure is changed, the change will be reflected if any of the variables is used for access. To avoid this, make a copy of the dictionary with copy().

Dictionary type()

The function type() applied to a dictionary returns: <class 'dict'>

To check whether an instance is a dictionary:

i = ...
if type(i) is dict:
  ...

For dict subclasses:

i = ...
if isinstance(i, dict):
  ...

Key Discussion

While the values of a dictionary can be any Python object, the keys have to be immutable object like scalar types (int, float, string) or tuples that contains immutable objects. They need to be hashable. See Dictionaries and the _hash_() Function below.

The keys 1 and True are equivalent. Why?

None as Key

None is a valid key:

d = {}
d[None] = 10
d['A'] = 20
assert len(d.keys()) == 2
assert d[None] == 10
assert d['A'] == 20

Dictionaries and the __hash__() Function

Dictionaries and the __hash__() Function

Dictionaries and the == Operator

It would be nice if == recursively deep-compares maps, and it seems to be working, Research this and come up with a definitive conclusion.

Create a Dictionary

A new dictionary instance is declared using the {...} syntax. The curly braces are placed around comma-separated key: value pairs. The dictionary can be empty

d = {}

or it can be populated with values:

d = {'a': 'b', 1: 2}

It is good form to insert a space after :. A comma is tolerated after the last pair.

Converting Other Data Structures to a Dictionary

Any two-value sequences can be converted into a dictionary using the dict() function.

Examples:

# list of lists
l = [['a', 'b'], ['c', 'd'], ['e', 'f']]
assert dict(l) == {'a': 'b', 'c': 'd', 'e': 'f'}

# list of tuples
l = [('a', 'b'), ('c', 'd'), ('e', 'f')]
assert dict(l) == {'a': 'b', 'c': 'd', 'e': 'f'}

# tuple of two-item lists
l = (['a', 'b'], ['c', 'd'], ['e', 'f'])
assert dict(l) == {'a': 'b', 'c': 'd', 'e': 'f'}

# list of two-character strings
l = ['ab', 'cd', 'ef']
assert dict(l) == {'a': 'b', 'c': 'd', 'e': 'f'}

# tuple of two-character strings
l = ('ab', 'cd', 'ef')
assert  dict(l) == {'a': 'b', 'c': 'd', 'e': 'f'}

From a Key Sequence and a Value Sequence with zip()

The sequences must not be of the same type, and neither have the same length:

l = ['a', 'b', 'c', 'd']
t = (10, 22, 77, 99, 1010)

assert  dict(zip(l, t)) == {'a':10, 'b':22, 'c':77, 'd':99}

Also see:

zip()

With Comprehensions

Dictionary Comprehensions

Access a Dictionary

Test for Empty Dictionary

Empty dictionaries evaluate to False:

d1 = {}
if not d1:
    print("dictionary is empty")

Size of a Dictionary

The number of key is given by the len() function:

d = {...}
print(len(d))

Access Individual Elements

Individual dictionary elements can be accessed with the [] syntax or with the get() function.

Access with [] Syntax

The [] syntax can only be used with keys that exist in the dictionary:

d = {'a': 'b'}
print(d['a'])

An attempt to access an inexistent key will throw a KeyError exception. To avoid the exception, use get() instead, or test the existence of the key first.

However, a key can be explicitly set to None, and in this case, square bracket access to the key will return None:

d = {'a': None}
assert d['a'] is None

get() Function

The get() will return the associated value, or None if the key does not exist.

d = {'a': 'b'}
assert d.get('a') == 'b'
assert d.get('no-such-key') is None

The get() function allows for a second argument which will be returned instead of None in case the key does not exist:

assert d.get('no-such-key', 'alternative') == 'alternative'

Safely Navigate a Complex Data Structure

Suggestions on how to safely recursively navigate a complex data structure:

Safely Navigate a Complex Data Structure

Test the Existence of a Key

The existence of the key can be tested with in:

if 'some-key' in d:
  print("key exists")

The non-existence is tested with this idiom:

if 'some-key' not in d:
  print("key does not exist")

get() can also be used:

if d.get('some-key') is not None:
  print("key exists")

Get All Keys with keys() Function

d = {'a': 'b', 'c': 'd'}
print(d.keys())

In Python 3, the keys() will return a dict_keys(), which is an iterable view of keys. This is useful with large dictionaries because the runtime does not use the time and the memory to create and store a list that might not be used. In case you need a list, use list() to wrap the result of keys().

list(d.keys())

Get All Values with values() Function

d = {'a': 'b', 'c': 'd'}
print(d.values())

In Python 3, the values() will return a dict_values(), which is an iterable view of values. This is useful with large dictionaries because the runtime does not use the time and the memory to create and store a list that might not be used. In case you need a list, use list() to wrap the result of values().

list(d.values())

Get All Key-Value Pairs with items() Function

d = {'a': 'b', 'c': 'd'}
print(d.items())

In Python 3, the items() will return a dict_items(), which is an iterable view of items. This is useful with large dictionaries because the runtime does not use the time and the memory to create and store a list that might not be used. Use use list() to wrap the result of items(). The list contains tuples of key and values.

for i in list(d.items()):
    print('key:', i[0])
    print('value:', i[1])

Equivalent:

for (k, v) in list(d.items()):
    print('key:', k)
    print('value:', v)

Iterate over a Dictionary

Use a for loop:

Iterate over Keys

Use the dictionary itself as argument of in. This is equivalent with iterating over the results of the keys() function.

d = {'a':'A', 'b':'B', 'c':'C'}
for k in d:
    print(k)

This will display:

a
b
c

Iterate over Values

Apply the in operator to the result of the values() function:

d = {'a':'A', 'b':'B', 'c':'C'}
for v in d.values():
    print(v)

This will display:

A
B
C

Iterate over Both Keys and Values

Apply the in operator to the result of the items() function. Each element is a tuple containing the key and the corresponding value:

d = {'a':'A', 'b':'B', 'c':'C'}
for kvt in d.items():
    print(kvt)

This will display:

('a', 'A')
('b', 'B')
('c', 'C')

Variables corresponding to the elements of the tuple can be assigned in one step, operation known as tuple unpacking, so the following syntax where we assign the key and the value to their corresponding variable is valid:

d = {'a':'A', 'b':'B', 'c':'C'}
for k, v in d.items():
    print('key:', k, 'value:', v)

This will display:

key: a value: A
key: b value: B
key: c value: C

Modify a Dictionary

Add an Element

Individual elements can be added with the [] syntax:

d = {}
d['a'] = 'b'
assert d['a'] == 'b'

Handle Missing Keys with setdefault() and defaultdict()

If the values are containers that need initialization when a key is first added to the dictionary, setdefault() is a dictionary method that set a value to the given default or returns the value if the key already exists:

d = {}
d.setdefault('a', []).append('b')
assert d['a'] == ['b']

TODO IPy Page 116.

Modify Individual Elements

Modification with [] Syntax

Individual elements can be modified with the [] syntax. If the key does not exist, the key-value pair will be added to the dictionary. If the key exists, the associated value will be updated:

d = {}
d['a'] = 'b'
assert d['a'] == 'b'
d['a'] = 'c'
assert d['a'] == 'c'

The syntax can be used with both literals and variables:

d = {}
d['a'] = 'b'
k = 'c'
d[k] = 'd'
assert d['c'] == 'd'

Delete Individual Element

With del

d = {'a': 'b', 'c': 'd'}
del d['a']
print(d) # will display {'c': 'd'}

Note that if the key being deleted does not exist, the [] syntax will throw an KeyError exception. The solution is to guard with an if:

if 'a' in d:
  del d['a']

With pop()

pop() returns the value for the key provided as argument, and removes the key, if the key is in the dictionary. Throws KeyError if the key is not in the dictionary.

d = {'a': 'A'}
assert d.pop('a') == 'A'
assert len(d) == 0

The missing key can be recovered from the KeyError instance. The name of the missing key, as string, is propagated as the exception message as the first element of the args tuple, and can be accessed as such:

try:
   ...
except KeyError as e:
   print(e.args[0])

Delete All Items

Use the clear() function:

d = {...}
d.clear()

Combine Dictionaries

Two dictionaries can be combined with update():

d1 = {'a': 'b'}
d2 = {'c': 'd'}
d1.update(d2) # d1 will become {'a': 'b', 'c': 'd'}, d2 will remain unchanged

If the same key exists in both dictionaries, the value associated with the key in the second dictionary will take precedence.

Copy a Dictionary

d1 = {'a': 'b'}
d2 =d1.copy()
d1['a'] = 'c'
print(d1['a']) # will display 'c'
print(d2['a']) # will display 'b'

Recursively Merge Dictionaries

Use update(). This only works on shallow trees, it fails to do deep recursive merge. Any existing keys in the data passed to update will have their old values discarded.

dict1 = {
    'a': 'A',
}

dict2 = {
    'b': 'B',
    'c': ['C', 'D']
}

dict1.update(dict2)

assert dict1 == {
    'a': 'A',
    'b': 'B',
    'c': ['C', 'D']
}

Ordered Dictionary

TODO IPy Page 120.

Subclass a Dictionary

class StorageClass(dict):
    def __init__(self, storage_class_config):
        super().__init__()
        name = None
        if storage_class_config:
            name = storage_class_config.get('name')
        if not name:
            raise ValueError("invalid 'config.csi_driver.storage_classes' element: missing 'name'")
        self['name'] = name
        self['volumeBindingMode'] = 'WaitForFirstConsumer'
        self['reclaimPolicy'] = 'Delete'
        self['allowVolumeExpansion'] = True
        self['parameters'] = {
            'type': 'gp3',
            'encrypted': 'true',
            'csi.storage.k8s.io/fstype': 'ext4',
            'throughput': '125',
            'iops': '3000'
        }