Метка: ключи

Задача на ключи словаря

Имеется такой код, где мы делаем 5 записей в словарь:

d = {}

d[float('nan')] = 1
d[float('nan')] = 2
d[1.0] = 'float'
d[1] = 'int'
d[True] = 'bool'

print(len(d))

Давайте решим ее. Для ключа словаря нам важны две вещи:

  • Хэш hash(key) – ключи с разными хэшами дадут нам разные записи в словаре.
  • Равенство ключей – если хэши равны, то проверяется равенство ключей (==), и если и они равны, то считается, что ключ тот же самый – это будет одна и та же запись в словаре.

float(‘nan’)

float('nan') – создает нам новый объект типа float со значением NaN (not a number – не число). Это специально значение. Оно получается, если результат операции не определен. Например, вычитание бесконечности из бесконечности не даст нам конкретного определенного результата, потому что бесконечность – это не число:

>>> print(float('Inf') - float('Inf'))
nan

В соответствии с IEEE 754, такое состояние задаётся через установку показателя степени в зарезервированное значение 11…11, а мантиссы — во что угодно, кроме 0 (зарезервированное значение для машинной бесконечности).

У NaN есть замечательно свойство, что он не равен никакому другому float, даже самому себе или другому NaN.

>>> x = float('nan')
>>> x == x
False
>>> hash(x)
0

Но hash от NaN всегда равен 0. Таким образом, словарь видит, что мы кладем в него ключи с одинаковым хэшем, но не равные между собой. Вывод: мы можем создать сколько угодно ключей с NaN, на вид они одинаковые, даже побитово могут совпадать, но так как каждый NaN не равен другому NaN по определению, то и dict все их считает разными!

>>> d = {}
>>> d[float('nan')] = 1
>>> d[float('nan')] = 2
>>> d
{nan: 1, nan: 2}

>>> d[float('nan')]
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
KeyError: nan

1, 1.0 и True

Пользуемся той же логикой: сначала проверяем хэши, потом, если они равны, то на равенство:

>>> hash(1), hash(1.0), hash(True)
(1, 1, 1)
>>> 1 == 1.0 == True
True

Все они равны между собой! Поэтому в словаре все эти три ключа будут отвечать ровно одной записи! Посмотрите:

>>> d = {}

>>> d[1.0] = 'float'
>>> d[1] = 'int'
>>> d[True] = 'bool'

>>> d
{1.0: 'bool'}
>>> len(d)
1

>>> d[1]
'bool'
>>> d[1.0]
'bool'
>>> d[True]
'bool'

Так как первая запись была с 1.0, то и ключ останется типа float, а значение уже будет перезаписано будущими операторами присваивания.

Ответ: 3

У нас в словаре две записи от разных float('nan') и только одна запись от трех присваиваний 1.0, 1 и True. Итого ответ – 3 (три) записи будет в словаре!

Пусть вас не путает, что в условии задачи было 5 операторов.

Специально для канала @pyway. Подписывайтесь на мой канал в Телеграм @pyway 👈 

🔐 Храним секреты правильно

Наверное, каждый когда-то писал в своем коде:

DB_HOST = 'localhost'
DB_USER = 'root'
DB_PASSWORD = 'l33thAxor666'

Это небезопасно и неудобно. Можно утащить доступы прямо из кода с машины или из репозитория. Можно хранить секретные данные в отдельных файлах конфигурации, передавать через переменные среды, но зачем это, если у современных ОС уже есть встроенные защищенные хранилища.

Для хранения секретов и паролей придет на помощь библиотека keyring.

В зависимости от ОС и среды она использует:
• macOS Keychain
• Freedesktop Secret Service
• KDE4 & KDE5 KWallet
• Windows Credential Locker
• и другие бэкенды…

Мы храним в скрипте или конфиге только название системы и логин (можете использовать произвольные):

>>> import keyring
>>> keyring.set_password("my_system", "my_username", "password")
>>> keyring.get_password("my_system", "my_username")
'password'

Другие пользователи системы не смогут прочитать эти данные. Но от вашего имени можно получить доступ к ним даже из терминала:

$ keyring set my_system my_username
Password for 'my_username' in 'my_system':
$ keyring get my_system my_username
qwerty

Считать пароль безопасно с клавиатуры можно с помощью модуля getpass (он строен в Python). Вводимые символы не будут видны на экране:

>>> import getpass
>>> password = getpass.getpass(prompt="Enter super password:")
Enter super password:
>>> password
'qwerty'

Специально для канала @pyway.