Содержание
Иногда сложно найти в Сети правильные, а главное актуальные «best practices» для языка. Документация, конечно же, содержит всю необходимую информацию, но отсеять нужную вещь в абзацах подробного (на то она и документация) описания, довольно сложно. Но недавно мне улыбнулся поиск Google, и я наткнулся на очень полезные «паттерны» языка Python от одного из core разработчиков — Raymond Hettinger.
Примечание: Все рекомендации даны в нескольких вариантах: сначала идут самые «плохие» варианты, а дальше предлагается лучшая альтернатива. Актуально для версии языка 2.7, отличия для версии 3.3 читайте в примечаниях к конкретному «паттерну».
Цикл по массиву из чисел
Плохо: иногда пишут так.
for i in [0, 1, 2, 3, 4, 5]: print i**2
Хорошо: лучший, с генератором. Но в 32 битной системе список из миллиона чисел будет занимать ~ 32 mb памяти.
for i in range(6): print i**2
Отлично: само лучший вариант. В отличии от второго xrange
возвращает только одно значение за раз, и не нужно лишнюю память для хранения всего массива.
for i in xrange(6): print i**2
Примечание: В версии Python 3.3 xrange
уже в ядре и называеться просто range
.
Цикл по списку
Плохо: часто бывшие С программисты пишут так.
colors = ['red', 'green', 'blue', 'yellow'] for i in range(len(colors)): print colors[i]
Хорошо: лучший вариант.
colors = ['red', 'green', 'blue', 'yellow'] for color in colors: print color
Но если нужно пройти по списку задом на перед?
Плохо: опять, прошло из C дает о себе знать:
colors = ['red', 'green', 'blue', 'yellow'] for i in range(len(colors)): print colors[i]
Хорошо: но в Python пишут вот так:
colors = ['red', 'green', 'blue', 'yellow'] for color in reversed(colors): print color
Цикл по списку с индексами
Плохо тоже что и выше.
colors = ['red', 'green', 'blue', 'yellow'] for i in range(len(colors)): print i, '-->', colors[i]
Хорошо: более элегантный вариант:
colors = ['red', 'green', 'blue', 'yellow'] for i, color in enumerate(colors): print i, '-->', color
Цикл по двум спискам
Плохо тоже что и выше.
names = ['raymond', 'rachel', 'matthew'] colors = ['red', 'green', 'blue', 'yellow'] n = min(len(names), len(colors)) for i in range(n): print names[i], '-->', colors[i]
Хорошо: делаем с двух списков один список кортежей. Проблема в том что zip
использует больше памяти чем первый вариант.
names = ['raymond', 'rachel', 'matthew'] colors = ['red', 'green', 'blue', 'yellow'] for name, color in zip(names, colors): print name, '-->', color
Отлично: в отличии от zip
, izip
использует кэширование, что помогает существенно сэкономить память.
names = ['raymond', 'rachel', 'matthew'] colors = ['red', 'green', 'blue', 'yellow'] for name, color in izip(names, colors): print name, '-->', color
Примечание: В версии Python 3.3 izip
вписан в ядро и называеться просто zip
.
Сортировка списка по алгоритму
Плохо: используя функцию для сравнения.
colors = ['red', 'green', 'blue', 'yellow'] def compare_length(c1, c2): if len(c1) < len(c2): return -1 if len(c1) > len(c2): return 1 return 0 print sorted(colors, cmp=compare_length)
Хорошо: используя сортировку по ключу. Использует намного меньше памяти.
colors = ['red', 'green', 'blue', 'yellow'] print sorted(colors, key=len)
Примечание: Метод cmp
убран с ядра Python 3.x.
Цикл по ключам словаря
Обычный способ возвращает ключи. При таком цикле происходит итерация словаря, поэтому в процессе его изменять нельзя.
for k in d: print k
Для изменения словаря в цикле используйте цикл по ключам (Пример: удаление всех ключей начинающихся с R
):
for k in d.keys(): if k.startswith('R'): del d[k]
В этом случае d.keys()
делает копию ключей словаря, что позволяет нам свободно работать с оригинальной структурой.
Цикл по ключам и значением словаря
Плохо: цикл по ключам и возвращение значение по последним. Медленный способ:
for k in d: print k, '-->', d[k]
Хорошо: быстрее делать цикл по значениях:
for k, v in d.items(): print k, '-->', v
Отлично: Но само лучший и быстрый способ это использовать итератор:
for k, v in d.iteritems(): print k, '-->', v
Соединение двух списков в один словарь
Очень быстрый метод, используется только один кортеж для генерации словаря.
names = ['raymond', 'rachel', 'matthew'] colors = ['red', 'green', 'blue'] d = dict(izip(names, colors)) # d будет иметь следующее значение: # {'matthew': 'blue', 'rachel': 'green', 'raymond': 'red'}
Подсчет элементов в словаре
Плохо: обычный способ:
colors = ['red', 'green', 'red', 'blue', 'green', 'red'] d = {} for color in colors: if color not in d: d[color] = 0 d[color] += 1 #{'blue': 1, 'green': 2, 'red': 3}
Хорошо: использует функцию get()
:
colors = ['red', 'green', 'red', 'blue', 'green', 'red'] d = {} for color in colors: d[color] = d.get(color, 0) + 1
Отлично: само продвинутый способ это использовать defaultdict()
. Но вы должны знать как он работает.
d = defaultdict(int) for color in colors: d[color] += 1
Группирование элементов списка
Плохо: если нужно сгруппировать элементы списка по некоторому признаку (в примере — длинна строки) часто используют такой метод:
names = ['raymond', 'rachel', 'matthew', 'roger', 'betty', 'melissa', 'judith', 'charlie'] d = {} for name in names: key = len(name) if key not in d: d[key] = [] d[key].append(name) {5: ['roger', 'betty'], 6: ['rachel', 'judith'], 7: ['raymond', 'matthew', 'melissa', 'charlie']}
Хорошо: но есть способ гораздо элегантней и быстрее:
d = defaultdict(list) for name in names: key = len(name) d[key].append(name)
Итог
На сегодня все. Надеюсь эти тривиальные, но полезные примеры помогут кому-то улучшить свой код, как они помогли это сделать мне. Их автором является Raymond Hettinger (@raymondh), Python Core Developer.