exit и компания

Выхода нет. Человек стучится в закрытую дверь, одиноко стоящую в поле (хотя может ее обойти).
>>> exit

У каждого, наверное, было: пишешь в интерпретаторе exit, а он:

>>> exit
Use exit() or Ctrl-D (i.e. EOF) to exit

Что же такое exit? Оказывается это такой класс, а текст — это всего лишь его repr:

>>> type(exit)
<class '_sitebuiltins.Quitter'>
>>> repr(exit)
'Use exit() or Ctrl-D (i.e. EOF) to exit'

А еще есть quit – он тоже из этой семьи:

>>> type(quit)
<class '_sitebuiltins.Quitter'>

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

try:
    # выбери любое из:
    exit()
    quit()
except SystemExit:
    print('Невозможно покинуть Омск')

Есть еще sys.exit, который тоже бросает SystemExit, что может быть пойман.

🛑 Вывод: нельзя надеятся на exit() для гарантированного завершения программы, ведь ваш код может быть обернут в try / except Exception, который может подавить SystemExit. Как же быть? Есть способ – это os._exit, который завершит программу на системном уровне:

import os
try:
    os._exit(-1)
except SystemExit:
    print('Невозможно покинуть Омск')
finally:
    print('Я свободен!')

Ни первый, ни второй print не сработают!

✋ Надо упомянуть еще os.abort(), которая также немедленно завершает программу сигналом SIGABRT, что еще дополнительно приводит к созданию дампа памяти. Причем, не будет вызван даже обработчик сигнала, установленный через signal.signal(). Функция os.abort() подходит только для аварийного завершения приложения.

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

​​Сортировка пузырьком

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

Сегодня простая, но важная тема. Алгоритм сортировки пузырьком, его проходят на курсах, его часто спрашивают на собеседованиях. Сортировка — это процесс выстраивания массива или списка по возрастанию или убыванию. На примере чисел: [3, 1, 4, 2] → [1, 2, 3, 4].

Смысл пузырьковой сортировки заключается в следующем: мы начинаем с начала списка и сравниваем элементы попарно (нулевой и первый), если нулевой больше первого, то меняем их местами. Независимо от того, была ли замена или нет, мы шагаем вправо и сравниваем элементы вновь. Если на прошлом шаге была замена, то на этом шаге у нас окажется тот же элемент, и если он опять оказался больше, то «всплывет» снова вправо. Так за один проход наибольший элемент всплывет в самый-самый конец списка, подобно тому, как пузырек воздуха всплывает в бутылке воды. Когда все пузырьки всплывут – список будет отсортирован.

📎 Пример: a = [3, 1, 4, 2] – 4 элемента:

Первый проход:
  1. Сравним a[0] = 3 и a[1] = 1, 3 > 1. Меняем их местами. Теперь a = [1, 3, 4, 2].
  2. Сравним a[1] = 3 и a[2] = 4, 3 < 4. Менять не надо.
  3. Сравним a[2] = 4 и a[3] = 2, 4 > 2. Меняем. a = [1, 3, 2, 4].

Проход окончен. 4 «всплыла» в самый конец списка на свое место a[3]. Поэтому мы не трогаем больше конец списка, но список еще не отсортирован до конца, и следующий проход будет рассматривать только первые 3 элемента списка.

Второй проход:
  1. Сравним a[0] = 1 и a[1] = 3, 1 < 3. Менять не надо.
  2. Сравним a[1] = 3 и a[2] = 2, 3 > 2. Меняем их. a = [1, 2, 3, 4]. Проход окончен.
Третий проход:
  1. Сравним a[0] = 1 и a[1] = 3, 1 < 3. Менять не надо. Список отсортирован. Можно выходить.

👨‍💻 Переходим к реализации на Python:

def bubble_sort(a):
    n = len(a)
    
    # номер прохода i = 0..(n-2), т.е. (n-1 раз):
    for i in range(n - 1):
        # номер сравнения j = 0..(n - i - 2)
        for j in range(n - i - 1):
            # сравниваем только соседние элементы
            if a[j] > a[j + 1]:
                a[j], a[j + 1] = a[j + 1], a[j]

Алгоритм прост, но можно запутаться в индексах: с какого элемента и куда бежать, что с чем сравнивать. Как лучше запомнить:

  • Начинаем всегда с начала (0-го элемента).
  • Число проходов меньше на 1, чем число элементов
  • С каждым проходом мы делаем все меньше и меньше сравнений, так как сортированный хвост списка растет на 1 после каждого прохода
  • Сравниваем только соседние элементы a[j] > a[j + 1], (а не i и j).
  • Если знак сравнения перевернуть, то сортировка будет по убыванию.

Временная сложность алгоритма квадратичная O(n^2) – имеются два вложенных цикла по элементам. Поэтому алгоритм медлителен для больших списков.  В реальной жизни чаще применяются другие алгоритмы сортировки, но пузырек до сих пор не забывают преподавать и спрашивать.

Анимация сортировки пузырьком

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

📕 Удаление ключа из словаря

Словарь (dict) – изменяемый тип в Python. Из словаря можно легко удалить ключ оператором del:

>>> d = {"foo":123, "bar":321}
>>> del d["foo"]
>>> d
{'bar': 321}

Что если ключа не окажется в словаре? Ответ: исключение – KeyError:

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

Конечно, можно сделать так:

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

Или даже так:

try:
    del d['baz']
except KeyError:
    pass

Однако, есть способ удалить ключ (которого возможно нет) в одну строчку:

d.pop('baz', None)

Обратите внимание, что второй аргумент None обязателен. Кроме того, метод pop вернет удаленный элемент, что может быть полезно в каких-то случаях.

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

📂 Склеиваем пути правильно

Так делать плохо:

my_path = root + '/' + user + '/' + filename

Потому что:

• В разных ОС – разные разделители пути: ‘/’ для nix-подобных и macOS, ‘\\’ для Windows

• В компонентах могут быть или не быть слеши – легко допустить ошибку

• Набирать это даже не удобно (имхо)

Самый простой способ правильного склеивания путей – os.path.join выберет нужный разделитель и расставит его как надо:

my_path = os.path.join(root, user, filename)

Есть еще более современный и удобный способ, который также поставляется в стандартной библиотеке Python – модуль pathlib. Это библиотека для работы с путями и файлами в стиле ООП. Примечательно, что объект Path поддерживает оператор /, который собственно и склеивает пути:

my_path = Path(root) / user / filename

У класса Path есть куча методов для получения путей в разных форматах, извлечения компонент пути, получении инфо о файлах и папках и много другое. Вот лишь некоторые из них:

>>> Path('~').expanduser()
PosixPath('/Users/bob')
>>> Path('~/../../usr').expanduser().resolve()
PosixPath('/usr')

>>> Path.cwd()
PosixPath('/Users/bob')

>>> Path('/usr/bin/foo').parts
('/', 'usr', 'bin', 'foo')

>>> Path('my/library.tar.gar').suffixes
['.tar', '.gar']

>>> Path('my/library.tar.gar').parent
PosixPath('my')

>>> str(Path('/usr/bin/foo'))
'/usr/bin/foo'

>>> sorted(Path('Projects/playground_python').glob('*.py'))
[PosixPath('Projects/playground_python/btc_gen.py'), PosixPath('Projects/playground_python/getattr.py'), ...]

>>> Path('test.txt').touch()
>>> Path('test.txt').exists()
True
>>> Path('test.txt').is_file()
True
>>> Path('test.txt').is_dir()
False
>>> Path('test.txt').is_symlink()
False

>>> Path('temp/1/foo').mkdir(parents=True, exist_ok=True)
>>> Path('temp/1/foo').resolve().as_uri()
'file:///Users/bob/temp/1/foo'
>>> Path('temp/1/foo').rmdir()

И еще очень много всего!

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

Пары из списка

В Python есть элегантный прием, который позволяет получить пары соседних элементов из списка. Нужно использовать функцию zip, передав в нее сам список и его же со сдвигом 1:

a = [1, 2, 3, 4, 5, 6]

for x1, x2 in zip(a, a[1:]):
    print(x1, x2)

Вывод:

1 2
2 3
3 4
4 5
5 6

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