Перечисления (Enum)

В Python нет специального синтаксиса для перечислений, зато есть модуль enum и класс Enum в нем, от которого можно отнаследоваться для создания собственного перечисления:

from enum import Enum
class Color(Enum):
    RED = 1
    GREEN = 2
    BLUE = 3

Задавать переменные этого типа можно несколькими способами:

c = Color(2)     # по значению
c = Color['RED'] # по строковому имени
c = Color.RED    # по члену класса

Значения из Enum человеко-читаемы при печати:

>>> print(Color.RED)
Color.RED

А также:

>>> Color.RED.name
'RED'
>>> Color.RED.value
1

Для сравнения эквивалентности используют оператор is (хотя == и != тоже работают):

if c is Color.RED:
    print('Red!')
if c is not Color.BLUE:
    print('Not blue!')

Для нескольких значений можно использовать in:

if c in (Color.BLUE, Color.GREEN):
    print('No red at all!')

Если неохота задавать значение самостоятельно, можно делать это автоматически:

from enum import Enum, auto
class Numbers(Enum):
    ONE = auto()
    TWO = auto()
    THREE = auto()
    FOUR = auto()

Члены перечислений хэшируемы и могут быть ключами словаря:

apples = {}
apples[Color.RED] = 'sweet'
apples[Color.GREEN] = 'sour'
>>> apples
{<Color.RED: 1>: 'sweet', <Color.GREEN: 2>: 'sour'}

Кратко создать перечислимый тип можно в функциональном стиле:

from enum import Enum
Animal = Enum('Animal', 'ANT BEE CAT DOG')

Семантика такого определения напоминает namedtuple. Первый аргумент – название перечисления, а второй – строка, где через пробел указаны названия вариантов. Пользоваться таким Enum можно также, как и заданным, через класс (см. выше), единственное, что могут быть проблемки с pickle

Допускается множество способов определить имен и значений вариантов перечисления: в строке через пробел или запятую, списком, списком кортежей имя-значение, словарем:

Animal = Enum('Animal', 'ANT, BEE, CAT, DOG')
Direction = Enum('Direction', ['NORTH', 'SOUTH', 'WEST', 'EAST'])
Color = Enum('Color', [('CYAN', 4), ('MAGENTA', 5), ('YELLOW', 6)])
Mood = Enum('Mood', {'HAPPY': ':-)', 'SAD': ':-('})

P.S. К сожалению, не все современные IDE понимают такое определение Enum, даже последний PyCharm ругается на аргументы и не активирует авто-дополнение по вариантам. Надеюсь, в будущем ситуация изменится в лучшую сторону.

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

Декораторы в Python

Вероятно, почти каждый разработчик на Python сталкивался с декораторами, видя конструкцию с со знаком @:

@app.route('/')
def index():
    return "Hello, World!"

Разберемся, что такое декоратор, и как он работает. Этот вопрос часто спрашивают на собеседованиях.

Декоратор – это функция, которая принимает как аргумент другую функцию*. Цель декоратора – расширить функциональность переданной ему функции без непосредственного изменения кода самой функции. Вот и все!

* Примечание: декорировать можно и класс, но об этом расскажу потом!

В Python функция – тоже объект, и ее можно передавать как аргумент, возвращать из другой функции, ей также можно назначать атрибуты.

Символ собачка (@) – всего лишь синтаксический сахар:

@decorator
def foo():
    ...

# эквивалентно:

def foo():
    ...
foo = decorator(foo)

ЧИТАТЬ ДАЛЬШЕ, КАК СОЗДАТЬ ДЕКОРАТОР…

Куча и очередь с приоритетом

Порядок обхода кучи

Очередь с приоритетом – это такая коллекция, которая поддерживает обязательно две следующие операции: вставка элемента с некоторым приоритетом (это может быть число или другой сравнимый объект) и извлечение элемента с наибольшим (или наименьшим приоритетом).

ЧИТАТЬ ПОДРОБНЕЕ

Перенаправление стандартного вывода

Случается так, что некий код (возможно, не ваш) пишет в стандартный вывод какую-то нужную информацию. Ее нетрудно перехватить с помощью функции redirect_stdout из стандартного модуля contextlib.

redirect_stdout является контекст менеджером (применяется совместно с with) и принимает аргументом файло-подобный объект (это может быть и дескриптор файла, и StringIO). 

📎 Пример. Сохраним вывод функции help в строку (в интерпретаторе пример работает некорректно, запускайте с файла):

import io
from contextlib import redirect_stdout

f = io.StringIO()
with redirect_stdout(f):
  help(pow)
s = f.getvalue() 
print(s) # в s будет вывод

📎 Пример. Или в файл:

with open('help.txt', 'w') as help_file:
  with redirect_stdout(help_file):
    help(pow)   

📎 Пример. stdout в stderr:

import sys
with redirect_stdout(sys.stderr):
  help(pow)

Во время работы redirect_stdout вывод в терминал попадать не будет.

Функция redirect_stderr аналогично перехватывает вывод из стандартного потока ошибок (stderr).

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

Многоликий else

Все знают, что ключевое слово else служит для выполнения альтернативной ветки кода, если условие if не выполнилось:

x = 5
if x < 3:
    print("x < 3")
else:
    print("x >= 3")

Но знали ли вы, что есть еще два примения else?

1. for/else, while/else

Если поставить else после тела цикла, то код по else будет выполнен только в том случае, если цикл завершился «нормально», т.е. в цикле не исполнилось break. Пример:

stack = [1, 3, 5, 7]
while stack:
    if stack.pop() == 4:
        break
else:
    print('not found!')

Пример. Простые числа и множители:

for n in range(2, 10):
    for x in range(2, n):
        if n % x == 0:
            # есть делитель, уходим
            print(n, '=', x, '*', n/x)
            break
    else:
        # цикл не нашел делителей
        print(n, 'простое число!')
2. try/else

В блоке try код else выполняется только в том случае, если не возникло исключений. else можно написать только после блока except, без него – нельзя. Порядок выполнения кода соответствует порядку написания сверху вниз: tryexcept или elsefinally.

try:
    ...
except Exception:
    print('Exception!')
else:
    print('Ok!')
finally:
    print('Bye!')

Причем else не будет также вызван, если сработавшее исключение не подпало под перечисленные except и не было обработано.

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