Python3, словари и их методы

avm

Опубликован:  2018-02-15T06:52:02.133677Z
Отредактирован:  2018-02-15T06:50:23.831157Z
700
Описание ещё одного встроенного типа данных Python3 - словарей, а так же справочная информация и примеры использования для каждого из имеющихся в арсенале этого типа объектов методов и операций. Спойлеры ниже кликабельны.

1. Общие сведения

Продолжаем разбираться со встроенными типами данных Питона. На этот раз я обращу своё самое пристальное внимание на словари, их создание, изменение и другие процедуры, предусмотренные диалектом для этого типа данных.

Словарь - это неупорядоченная последовательность не повторяющихся ключей, в которой каждому ключу соответствует некоторое значение. Значениями словаря могут быть объекты любых определённых диалектом типов. Ключами словаря могут быть только неизменяемые объекты (числа, строки, тюплы etc.). Словарь является изменяемой коллекцией данных любых типов объединённой общим именем. В Питоне третьей версии словари определяются классом dict модуля builtins стандартной библиотеки.

Создать словарь можно вызовом класса dict. Если этот вызов не содержит аргументов, то созданный словарь окажется пустым.

>>> sample = dict()
>>> sample
{}
>>> 

Аналогичный результат будет получен, если имени переменной присвоить специальное значение {}.

>>> sample = {}
>>> sample
{}
>>> 

Вызов dict допускает передачу аргументов в виде ключ=значение, в этом случае словарь будет создан из переданных аргументов.

>>> sample = dict(first='first', second=2, third=['a', 'b'])
>>> sample
{'first': 'first', 'third': ['a', 'b'], 'second': 2}
>>> 

Словарь можно создать специальным выражением, явным образом перечислив пары ключ: значение.

>>> sample = {'first': 1, 'second': 'b', 'third': ['hello']}
>>> sample
{'first': 1, 'second': 'b', 'third': ['hello']}
>>> 

Важное замечание: при создании словаря следует иметь ввиду, что ключи в словаре не должны повторяться, а при попытке повторить какой-то ключ исключение не генерируется и значение повторяющегося ключа будет соответствовать последнему введённому для этого ключа значению.

>>> sample = {'a': 'first', 'a': 'second', 'a': 'third'}
>>> sample
{'a': 'third'}
>>> 

Кроме этого, словарь можно создать из двух последовательностей при помощи встроенной функции zip вызовом dict.

>>> first = ['a', 'b', 'c', 'd', 'e']
>>> second = range(5)
>>> sample = dict(zip(first, second))
>>> sample
{'c': 2, 'd': 3, 'e': 4, 'b': 1, 'a': 0}
>>> 

Или специальным выражением - dict comprehension.

>>> first
['a', 'b', 'c', 'd', 'e']
>>> second
range(0, 5)
>>> sample = {key: value for key, value in zip(first, second)}
>>> sample
{'c': 2, 'd': 3, 'e': 4, 'b': 1, 'a': 0}
>>> 

При создании словарей следует правильно подбирать объекты для ключей словаря, только неизменяемые объекты могут быть ключами словаря. Изменяемые объекты ключами словаря быть не могут.

>>> sample = {['a']: 'this is the worst idea'}
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: unhashable type: 'list'
>>> 

Значения словаря могут содержать любые определяемые диалектом объекты: любые экземпляры любого из встроенных типов данных, классы, функции, анонимные функции, экземпляры классов etc.

>>> s = 'Hello, World!'
>>> x = [1, 3, 5]
>>> d = {'sample': 1}
>>> class A: pass
... 
>>> a = A()
>>> def act(some):
...     print(some)
... 
>>> sample = {'s': s, 'x': x.copy(), 'd': d.copy(), 'A': A, 'a': a, 'act': act}
>>> sample
{'d': {'sample': 1}, 'act': <function act at 0x7f6251409f28>, 's': 'Hello, World!', 'a': <__main__.A object at 0x7f62512ab400>, 'A': <class '__main__.A'>, 'x': [1, 3, 5]}
>>> 

Словарь в Питоне имеет ряд важных характерных особенностей, а именно:

  • словарь имеет длину;
  • словарь - это неупорядоченная последовательность;
  • словарь - это изменяемый объект (mutable object);
  • словарь - это итерируемый объект.

Рассмотрим эти особенности в деталях.

Словарь имеет длину. Длина словаря выражается целым числом и отражает количество пар ключ: значение, содержащихся в этом словаре. Длину словаря можно определить при помощи встроенной функции len модуля builtins стандартной библиотеки.

>>> sample = {'first': 123, 'second': 234, 'third': 345}
>>> len(sample)
3
>>> 

Следует обратить внимание, что пустой словарь тоже имеет длину.

>>> sample = {}
>>> len(sample)
0
>>> 

Основное отличие пустого словаря заключается в возвращаемом булевой операцией с этим словарём значении.

>>> empty = {}
>>> bool(empty)
False
>>> sample = {'a': 1}
>>> bool(sample)
True
>>> 

Словарь - это неупорядоченная последовательность. Ключи в словаре следуют в случайном порядке и не совпадают с порядком, который был задан при создании словаря.

>>> sample = {'a': 'a', 'b': 'b', 'c': 'c', 'd': 'd'}
>>> sample
{'d': 'd', 'a': 'a', 'b': 'b', 'c': 'c'}
>>> 

А при добавлении в словарь новых пар ключ: значение порядок следования элементов в словаре изменяется случайным образом.

>>> sample
{'d': 'd', 'a': 'a', 'b': 'b', 'c': 'c'}
>>> sample['e'] = 'e'
>>> sample
{'d': 'd', 'a': 'a', 'e': 'e', 'b': 'b', 'c': 'c'}
>>> sample['f'] = 'f'
>>> sample
{'d': 'd', 'e': 'e', 'f': 'f', 'a': 'a', 'b': 'b', 'c': 'c'}
>>> 

Словарь - это изменяемый объект (mutable object). На практике это означает, что словарь можно изменить на месте. При этом доступ к данным в словаре осуществляется по ключу. По ключу можно добавлять в словарь новые значения.

>>> sample
{'b': 'b', 'c': 'c'}
>>> sample['d'] = 'd'
>>> sample
{'d': 'd', 'b': 'b', 'c': 'c'}
>>> 

По ключу можно изменять существующие в словаре пары.

>>> sample
{'d': 'd', 'b': 'b', 'c': 'c'}
>>> sample['b'] = 123
>>> sample
{'d': 'd', 'b': 123, 'c': 'c'}
>>> 

По ключу можно удалять данные из словаря.

>>> sample
{'d': 'd', 'b': 123, 'c': 'c'}
>>> del sample['d']
>>> sample
{'b': 123, 'c': 'c'}
>>> 

Кроме того, по ключу можно просто получить доступ к соответствующему значению.

>>> sample
{'b': 123, 'c': 'c'}
>>> c = sample['c']
>>> c
'c'
>>> 

Следует иметь ввиду, что поскольку значениями словаря могут быть любые определяемые диалектом объекты, то при получении значения по ключу можно воспользоваться любыми свойствами хранящегося в соответствующем значении объекта, при этом следует знать тип этого объекта.

>>> sample = {'a': 'hello, world!'}
>>> s = sample['a'].capitalize()
>>> s
'Hello, world!'
>>> 

Изменяемость словаря также означает, что копирование словаря не следует осуществлять операцией присваивания значения, потому что в этом случае полученная копия будет тем же самым объектом, просто у исходного словаря появится ещё одна ссылка в пространстве имён.

>>> sample
{'a': 'hello, world!'}
>>> new_sample = sample
>>> new_sample is sample
True
>>> new_sample['b'] = 'stop it, please!'
>>> sample
{'a': 'hello, world!', 'b': 'stop it, please!'}
>>> 

Копирование словарей следует выполнять при помощи вызова dict или методом dict.copy.

>>> new_sample = dict(sample)
>>> new_sample is sample
False
>>> new_sample['b'] = None
>>> new_sample
{'a': 'hello, world!', 'b': None}
>>> sample
{'a': 'hello, world!', 'b': 'stop it, please!'}
>>> 

Метод dict.copy позволяет получить аналогичный результат.

>>> sample
{'a': 'hello, world!', 'b': 'stop it, please!'}
>>> new_sample = sample.copy()
>>> new_sample is sample
False
>>> new_sample
{'a': 'hello, world!', 'b': 'stop it, please!'}
>>> 

Словарь - это итерируемый объект. Любой словарь, в том числе пустой словарь поддерживает протокол итерации. Словари итерируются по ключам. При итерации словарей следует иметь ввиду их неупорядоченность.

>>> from string import digits
>>> sample = dict(zip(digits, range(10)))
>>> for key in sample:
...     print(key, '->', pow(sample[key], 2))
... 
9 -> 81
1 -> 1
8 -> 64
0 -> 0
5 -> 25
6 -> 36
7 -> 49
3 -> 9
2 -> 4
4 -> 16
>>> 

Важное замечание: при итерации словаря в цикле for попытка изменить длину итерируемого словаря в теле цикла станет причиной ошибки.

>>> sample
{'9': 9, '1': 1, '8': 8, '0': 0, '5': 5, '6': 6, '7': 7, '3': 3, '2': 2, '4': 4}
>>> for key in sample:
...     if pow(sample[key], 2) < 10:
...         print(key, '->', pow(sample[key], 2))
...         del sample[key]
... 
1 -> 1
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
RuntimeError: dictionary changed size during iteration
>>> 

В некоторых случаях при работе со словарём удобно итерировать не сам словарь, а упорядоченный список, состоящий из его ключей и полученный при помощи встроенной функции sorted из модуля builtins стандартной библиотеки.

>>> sample
{'9': 9, '1': 1, '8': 8, '0': 0, '5': 5, '6': 6, '7': 7, '3': 3, '2': 2, '4': 4}
>>> for key in sorted(sample):
...     if pow(sample[key], 2) < 10:
...         print(key, '->', pow(sample[key], 2))
...         del sample[key]
... 
0 -> 0
1 -> 1
2 -> 4
3 -> 9
>>> sample
{'9': 9, '8': 8, '5': 5, '6': 6, '7': 7, '4': 4}
>>> 

Класс dict определяет набор операций и методов, который позволяет сравнительно просто и без затрат производить со словарями необходимые манипуляции. Доступные словарям методы можно увидеть при помощи встроенной функции dir.

>>> dir(dict)[::-1]
['values', 'update', 'setdefault', 'popitem', 'pop', 'keys', 'items', 'get', 'fromkeys', 'copy', 'clear', '__subclasshook__', '__str__', '__sizeof__', '__setitem__', '__setattr__', '__repr__', '__reduce_ex__', '__reduce__', '__new__', '__ne__', '__lt__', '__len__', '__le__', '__iter__', '__init__', '__hash__', '__gt__', '__getitem__', '__getattribute__', '__ge__', '__format__', '__eq__', '__doc__', '__dir__', '__delitem__', '__delattr__', '__contains__', '__class__']
>>> 

Характерные для словарей операции и методы заслуживают отдельного внимания, сведения о них следуют далее по списку ниже.

Словари очень широко используются при программировании на Питоне, этот встроенный тип данных значительно упрощает реализацию многих задач и манипуляции с данными.

2. Методы: clear

Метод clear удаляет из исходного словаря все пары ключ: значение.

>>> sample = {'first': 'first item',
...           'second': 'second item'}
>>> sample
{'second': 'second item', 'first': 'first item'}
>>> sample.clear()
>>> sample
{}
>>> 

Следует иметь ввиду, что clear изменяет исходный словарь на месте и возвращает None.

>>> sample['again'] = 'this item will be removed'
>>> sample
{'again': 'this item will be removed'}
>>> sample.clear() is None
True
>>> sample
{}
>>> 

3. Методы: copy

Метод copy возвращает копию исходного словаря - новый словарь.

>>> sample
{'a': 0, 'd': 3, 'b': 1, 'c': 2, 'e': 4, 'f': 5}
>>> new_sample = sample.copy()
>>> new_sample == sample
True
>>> new_sample is sample
False
>>> 

4. Методы: fromkeys

Метод fromkeys принимает в качестве аргументов итерируемый объект и некоторое значение, возвращает словарь, в котором ключами будут элементы заданного итерируемого объекта, а значения будут эквиваленты заданному значению. Метод fromkeys не требует исходного словаря.

>>> keys
'abcde'
>>> sample = dict.fromkeys(keys, '#')
>>> sample
{'a': '#', 'b': '#', 'c': '#', 'e': '#', 'd': '#'}
>>> 

Если значение не задано, то все значения в полученном словаре будут равны None.

>>> keys
'abcde'
>>> sample = dict.fromkeys(keys)
>>> sample
{'a': None, 'b': None, 'c': None, 'e': None, 'd': None}
>>> 

Следует помнить, что ключами словаря могут быть только неизменяемые объекты, поэтому все элементы передаваемого методу fromkeys объекта должны соответствовать этому условию, иначе будет сгенерировано исключение.

>>> keys = [{}, [], 'a']
>>> sample = dict.fromkeys(keys)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: unhashable type: 'dict'
>>> 

5. Методы: get

Метод get принимает два аргумента, ключ и значение, и возвращает значение хранящееся в исходном словаре по заданному ключу.

>>> sample
{'a': 0, 'b': 1, 'c': 2, 'e': 4, 'd': 3}
>>> sample.get('a', 20)
0
>>> 

Если в исходном словаре нет заданного ключа, то метод get вернёт заданное вторым аргументом значение.

>>> sample
{'a': 0, 'b': 1, 'c': 2, 'e': 4, 'd': 3}
>>> sample.get('z', 20)
20
>>> 

Если второй аргумент не задан, и в исходном словаре нет заданного ключа, то метод вернёт дефолтное значение - None.

>>> sample
{'a': 0, 'b': 1, 'c': 2, 'e': 4, 'd': 3}
>>> sample.get('z') is None
True
>>> 

6. Методы: items

Метод items возвращает так называемый dictionary view object, состоящий из пар ключ, значение исходного словаря.

>>> sample
{'a': 0, 'b': 1, 'c': 2, 'e': 4, 'd': 3}
>>> sample.items()
dict_items([('a', 0), ('b', 1), ('c', 2), ('e', 4), ('d', 3)])

Возвращаемый этим методом объект поддерживает протокол итерации.

>>> for key, value in sample.items():
...     print(key, '->', value)
... 
a -> 0
b -> 1
c -> 2
e -> 4
d -> 3
>>> 

Важное замечание: при итерации этого объекта в цикле for следует избегать изменения длины исходного словаря в теле цикла, подобные действия повлекут за собой ошибку.

7. Методы: keys

Метод keys возвращает dictionary view object, состоящий из ключей исходного словаря.

>>> sample
{'a': 0, 'b': 1, 'c': 2, 'e': 4, 'd': 3}
>>> sample.keys()
dict_keys(['a', 'b', 'c', 'e', 'd'])
>>> 

Возвращаемый этим методом объект поддерживает протокол итерации.

>>> for key in sample.keys():
...     print(sample[key])
... 
0
1
2
4
3
>>> 

8. Методы: pop

Метод pop принимает два аргумента, ключ и значение, возвращает значение, хранящееся в исходном словаре в заданном ключе и удаляет эту пару из исходного словаря.

>>> sample
{'a': 0, 'b': 1, 'c': 2, 'e': 4, 'd': 3}
>>> sample.pop('d', 20)
3
>>> sample
{'a': 0, 'b': 1, 'c': 2, 'e': 4}
>>> 

Если заданный ключ в исходном словаре отсутствует, то метод вернёт заданное значение.

>>> sample
{'a': 0, 'b': 1, 'c': 2, 'e': 4}
>>> sample.pop('z', 20)
20
>>> sample
{'a': 0, 'b': 1, 'c': 2, 'e': 4}
>>> 

Если заданный ключ в исходном словаре отсутствует, а значение не задано, то будет сгенерировано исключение.

>>> sample
{'a': 0, 'b': 1, 'c': 2, 'e': 4}
>>> sample.pop('z')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
KeyError: 'z'
>>> 

9. Методы: popitem

Метод popitem возвращает тюпл, содержащий первую пару ключ, значение исходного словаря и удаляет эту пару из него.

>>> sample
{'a': 0, 'b': 1, 'c': 2, 'e': 4, 'd': 3}
>>> sample.popitem()
('a', 0)
>>> sample
{'b': 1, 'c': 2, 'e': 4, 'd': 3}
>>> 

Если исходный словарь - пустой, то метод сгенерирует исключение.

>>> sample
{}
>>> sample.popitem()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
KeyError: 'popitem(): dictionary is empty'
>>> 

10. Методы: setdefault

Метод setdefault принимает два аргумента, ключ и значение, и возвращает значение, хранящееся в исходном словаре по заданному ключу аналогично методу get.

>>> sample
{'a': 0, 'b': 1, 'c': 2, 'e': 4, 'd': 3}
>>> sample.setdefault('a', 20)
0
>>> sample
{'a': 0, 'b': 1, 'c': 2, 'e': 4, 'd': 3}
>>> 

При этом, если заданного ключа в исходном словаре нет, то метод setdefault добавляет в исходный словарь новую пару ключ: значение в соответствии с принятыми аргументами и возвращает заданное значение.

>>> sample
{'a': 0, 'b': 1, 'c': 2, 'e': 4, 'd': 3}
>>> sample.setdefault('z', 20)
20
>>> sample
{'a': 0, 'z': 20, 'd': 3, 'b': 1, 'c': 2, 'e': 4}
>>> 

Если заданного ключа в исходном словаре нет и значение не задано, то в исходный словарь добавляется заданный ключ со значением None.

>>> sample
{'a': 0, 'z': 20, 'd': 3, 'b': 1, 'c': 2, 'e': 4}
>>> sample.setdefault('w') is None
True
>>> sample
{'a': 0, 'z': 20, 'd': 3, 'w': None, 'b': 1, 'c': 2, 'e': 4}
>>> 

11. Методы: update

Метод update позволяет изменять значения в исходном словаре и добавлять в исходный словарь новые ключи из соответствующих задаче последовательностей. Работу метода лучше всего можно продемонстрировать конкретными примерами.

Update при помощи zip последовательности.

>>> letters
'abcde'
>>> numbers
[15, 16, 17, 18, 19]
>>> sample = {'a': 0}
>>> sample.update(zip(letters, numbers))
>>> sample
{'c': 17, 'a': 15, 'b': 16, 'd': 18, 'e': 19}
>>> 

Update при помощи словаря донора.

>>> sample
{'c': 17, 'a': 15, 'b': 16, 'd': 18, 'e': 19}
>>> donor = {'a': 'what?', 'd': 'stop it'}
>>> sample.update(donor)
>>> sample
{'e': 19, 'd': 'stop it', 'c': 17, 'a': 'what?', 'b': 16}
>>> 

Update при помощи zip последовательности и явно заданных ключей.

>>> sample
{'e': 19, 'd': 'stop it', 'c': 17, 'a': 'what?', 'b': 16}
>>> sample.update(zip(letters, [1, 3]), c=5, d=7, e=9)
>>> sample
{'e': 9, 'd': 7, 'c': 5, 'a': 1, 'b': 3}
>>> 

Update при помощи явно заданных ключей.

>>> sample
{'e': 9, 'd': 7, 'c': 5, 'a': 1, 'b': 3}
>>> sample.update(e=11, a=0, c=25)
>>> sample
{'e': 11, 'd': 7, 'c': 25, 'a': 0, 'b': 3}
>>> 

Следует иметь ввиду, что метод update изменяет исходный словарь на месте и возвращает None.

>>> sample
{'e': 11, 'd': 7, 'c': 25, 'a': 0, 'b': 3}
>>> sample.update(e=0.1, b='help') is None
True
>>> sample
{'e': 0.1, 'd': 7, 'c': 25, 'a': 0, 'b': 'help'}
>>> 

12. Методы: values

Метод values возвращает dictionary view object содержащий значения исходного словаря.

>>> sample
{'c': 2, 'a': 0, 'b': 1, 'd': 3, 'e': 4}
>>> sample.values()
dict_values([2, 0, 1, 3, 4])
>>> 

Возвращаемый этим методом объект поддерживает протокол итерации.

>>> for each in sample.values():
...     print(each)
... 
2
0
1
3
4
>>> 

13. Логические операции со словарями

Для словарей доступна следующая логика:

Сравнение двух словарей.

>>> sample
{'a': 0}
>>> sample2
{'a': 3, 'b': 2}
>>> sample == sample2
False
>>> sample is sample2
False
>>> 

Поиск заданного ключа в словаре.

>>> sample = {'a': 3, 'b': 4}
>>> 'a' in sample
True
>>> 'c' in sample
False
>>> 

Поиск заданного значения в словаре.

>>> sample
{'a': 3, 'b': 4}
>>> 3 in sample.values()
True
>>> 5 in sample.values()
False
>>> 

Поиск пары ключ, значение в словаре.

>>> sample
{'a': 3, 'b': 4}
>>> ('a', 0) in sample.items()
False
>>> ('a', 3) in sample.items()
True
>>> 
 
Осталось: 163
Комментарии: