Python: советы, трюки, идиомы

Представляем вашему вниманию перевод статьи, которая представляет из себя подборку полезных плюшек, если вы кодите на Python.

Я предпочитаю python по той причине, что писать код на нем мне удается быстрее. Но это еще не все, он имеет множество замечательных трюков и идиом, которые значительно облегчают жизнь. Для того чтобы не забывать об их использовании, а еще для общего интереса, я решил собрать из них коллекцию.

enumerate

Достаточно распространенная вещь - это цикл в списке, который также отслеживает индекс текущего элемента. Мы можем использовать переменную count, но python дает нам лучший синтаксис для этого в качестве функции enumerate().


	
students = ('James', 'Andrew', 'Mark')
for i, student in enumerate(students):
    print i, student
# вывод:
# 0 James
# 1 Andrew
# 2 Mark 

set

set это небольшая, но полезная структура данных, которая похожа на список, но каждое значение в ней уникально. Есть несколько полезных операций, помимо создания списка уникальных элементов, которые мы можем с ней сделать. Например, позволяет попробовать несколько способов проверки списков ввода.



colours = set(['red', 'green', 'blue', 'yellow', 'orange', 'black', 'white'])

# либо используйте новейший синтаксис для объявления списка.
input_values = {'red', 'black', 'pizza'}

# получаем список допустимых цветов
valid_values = input_values.intersection(colours)

print valid_values
# вывод set(['black', 'red'])

# получаем список недопустимых цветов
invalid_values = input_values.difference(colours)

print invalid_values
# вывод set(['pizza'])

# если что-то не так, выкидываем исключение
if not input_values.issubset(colours):
    raise ValueError("Invalid colour: " + ", ".join(input_values.difference(colours)))
	

Control statements

with

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



with open('/etc/passwd', 'r') as f:
    print f.read()
	

for ... else

Это интересная часть синтаксиса, которая позволяет запускать некоторый код, если цикл никогда не доходит до утверждения break. Благодаря этому необходимость отслеживать, сломалась или нет ваша переменная for, отпадает. Просто посмотрите мой код, вот псевдо-версия того, что делал я.



# какой-то код

for file_name in file_list:
    if is_build_file(file_name):
        break
else: # никаких break
    make_built_file()

# что-то другое здесь
	

Условные выражения

Python делает возможным использование условных выражений , поэтому вместо написания if .. else с присваиванием одной переменной в каждой ветке вы можете делать следующее:



# делаем число всегда нечетным 
number = count if count % 2 else count - 1

# вызываем функцию, если объект не None 
name = user.name() if user is not None else 'Guest'
print "Hello", name
	

Это одна из причин, по которой мне очень нравится python. Приведенный выше код очень легко читается, по сравнению с оператором ternary, который выглядит следующим образом , a ? b : c , и используется в других языках. Меня он всегда сбивает с толку.

Списковое включение

Списковое включение заменяет создание списка с помощью циклов и вызова append. Сравните следующее.




numbers = [1, 2, 3, 4, 5, 6, 7]
squares = []
for num in numbers:
    squares.append(num * num)

# со сжатием списка 
squares = [num * num for num in numbers]
	

Мы также можем усложнить это, добавив фильтрацию или условный оператор:



numbers = [1, 2, 3, 4, 5, 6, 7]

# квадраты всех нечетных чисел
squares = [num * num for num in numbers if num % 2]

# умножаем четные числа на 2 и нечетные на 3
mul = [num * 3 if num % 2 else num * 2 for num in numbers]
	

Выражения-генераторы

У списковых включений есть одна возможная проблема, они создают список сразу в памяти. Это может стать большой проблемой, если вы имеете дело с большими наборами данных. Но даже если это не так, лишние расходы вам не нужны. В случае если вы собираетесь запустить цикл по результатам, то нет смысла создавать список. Поэтому, если вы можете отказаться от возможности индексировать результат и выполнять другие операции с списками, вы можете использовать выражение генератора, которое использует очень похожий синтаксис, но создает “ленивый” объект, который ничего не вычисляет, до того момента пока вы не запросите значение.



# выражение-генератор для квадратов всех чисел
squares = (num * num for num in numbers)

# в противном случае вы получите проблему с памятью

with open('/some/number/file', 'r') as f:
    squares = (int(num) * int(num) for num in f)
    # делаем что-то с этими числами
	

Генераторы с использованием yield

Выражения-генераторы великолепны, но иногда вам нужно что-то, со схожими свойствами, но при этом не ограниченное синтаксисом, который используют генераторы. Используйте yield. Так, в примере ниже, будет создан генератор - бесконечный набор случайных чисел. Поэтому, пока мы продолжаем запрашивать случайное число, он с радостью нам их поставляет.




import random
def random_numbers(high=1000):
    while True:
        yield random.randint(0, high)
	

Словарное включение

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



teachers = {
    'Andy': 'English',
    'Joan': 'Maths',
    'Alice': 'Computer Science',
}
# используем  списковое включение
subjects = dict((subject, teacher) for teacher, subject in teachers.items())

# используем словарное включение 
subjects = {subject: teacher for teacher, subject in teachers.items()}

	

zip

Если вы считаете, что генерация бесконечного числа случайных int не была такой полезной, то здесь я хочу использовать ее для отображения другой функции, которую я люблю использовать zip(). zip()принимает несколько итераций и присоединяет n-й элемент каждого в кортеж- tuple. Например:



names = ('James', 'Andrew', 'Mark')
for i, name in zip(random_numbers(), names):
    print i, name

# вывод:
# 288 James
# 884 Andrew
# 133 Mark
	

Таким образом, он печатает все имена со случайным числом (от нашего генератора случайных чисел выше) рядом с именем. Обратите внимание, что zip()остановится, как только достигнет конца кратчайшего итерируемого. Если это нежелательно, модуль itertools имеет тот, который идет до конца самого длинного.
Мы могли бы также сделать что-то похожее, чтобы получить dict каждого имени, сопоставленного со случайным числом.



dict(zip(names, random_numbers()))

# вывод: {'James': 992, 'Andrew': 173, 'Mark': 329}
	

itertools

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

Collections

Python поставляется с модулем, который содержит несколько контейнеров из типов данных, называемых коллекциями. Сначала я хотел рассказать только о двух из них, но на данный момент их уже более трех и называются они namedtuple(), deque(связный список), и OrderedDict.

defaultdict

Этот тип пригодится, когда вы добавляете списки внутри словаря. Если вы используете dict(),то вам нужно будет проверить, существует ли ключ до добавления, но с defaultdict этого делать не нужно. Например.


 
from collections import defaultdict

order = (
    ('Mark', 'Steak'),
    ('Andrew', 'Veggie Burger'),
    ('James', 'Steak'),
    ('Mark', 'Beer'),
    ('Andrew', 'Beer'),
    ('James', 'Wine'),
)

group_order = defaultdict(list)

for name, menu_item in order:
    group_order[name].append(menu_item)

print group_order

# вывод
# defaultdict(, {
#     'James': ['Steak', 'Wine'],
#     'Andrew': ['Veggie Burger', 'Beer'],
#     'Mark': ['Steak', 'Beer']
# })

 

Мы могли бы также считать их вот так.



order_count = defaultdict(int)

for name, menu_item in order:
    order_count[menu_item] += 1

print order_count

# вывод
# defaultdict(, {
#     'Beer': 2, 
#     'Steak': 2, 
#     'Wine': 1, 
#     'Veggie Burger': 1
# })
	

Counter

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



from collections import Counter

order_count =  Counter(menu_item for name, menu_item in order)
print order_count

# вывод
# Counter({
#    'Beer': 2,
#    'Steak': 2,
#    'Wine': 1,
#    'Veggie Burger': 1
# })
	

Другой, может быть, лучший, пример может посчитать все уникальные строки, которые появляются в файле. Это делается очень просто.



with open('/some/file', 'r') as f:
    line_count = Counter(f)
	

Если вы хотите узнать больше, то книга Python Cookbook, третье издание O'Reilly Media -отличный источник информации. Но если вам нужно что-то более простое, попробуйте Learning Python, 5th Edition.

МЕРОПРИЯТИЯ

Комментарии

ВАКАНСИИ

Добавить вакансию
Java Team Lead
Москва, по итогам собеседования
Go-разработчик
по итогам собеседования

ЛУЧШИЕ СТАТЬИ ПО ТЕМЕ