Красота и изящность стандартной
библиотеки Python
Примеры использования
Макс Усачев
m_usachev@wargaming.net
Про группировку
элементов
Группировка элементов
Дана произвольная строка
import string
import random
RANDOM_STRING_LENGTH = 999
choices = string.ascii_letters + string.digits
random_string = [random.choice(choices) for 
i in xrange(RANDOM_STRING_LENGTH)]
Найти позиции каждого уникального символа этой строки в исходной
строке.
Группировка элементов
Первое, что приходит людям на ум
и они так это и оставляют:
result = {}
for index in xrange(len(random_string)):
element = random_string[index]
if element in result.keys():
result[element].append(index)
else:
result[element] = [index]
Группировка элементов
После тревожной ночи иногда
код становится чуть лучше:
result = {}
for index, element in enumerate(random_string):
if element in result:
result[element].append(index)
else:
result[element] = [index]
Группировка элементов
А потом ты понимаешь...
Что кода слишком много!
result = {}
for index, element in enumerate(random_string):
result.setdefault(element, []).append(index)
Группировка элементов
Казалось бы, куда уже короче ...
result = {}
for index, element in enumerate(random_string):
result.setdefault(element, []).append(index)
Группировка элементов
Модуль collections !
Модуль collections реализует высокопроизводительные
контейнерные типы данных.
Counter, deque, OrderedDict и defaultdict, а также функция
для создания типов данных - namedtuple().
Контейнеры, создаваемые при помощи этого модуля,
представляют собой альтернативу встроенным в Python
контейнерам общего назначения: dict, list, set и tuple.
Документация: http://docs.python.org/2/library/collections.html#module-collections
Исходный код: http://hg.python.org/cpython/file/2.7/Lib/collections.py
http://hg.python.org/cpython/file/2.7/Lib/_abcoll.py
Группировка элементов
Примеры использования defaultdict
Когда фабричная функция is None
>>> from collections import defaultdict
>>> d = defaultdict()
>>> d['foo']
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
KeyError: 'foo'
new in Python v2.5
Если фабричная функция не указана, при обращении по
несуществующему ключу ведет себя как обычный dict.
Группировка элементов
Примеры использования defaultdict
Когда фабричная функция is not None
>>> d = defaultdict(int)
>>> d['foo']
0
>>> d['foo'] += 1
>>> d
defaultdict(<type 'int'>, {'foo': 1})
>>> d['foo1'] += 5
>>> d
defaultdict(<type 'int'>, {'foo': 1, 'foo1': 5})
Группировка элементов
Примеры использования defaultdict
Когда фабричная функция is not None
>>> d = defaultdict(list)
>>> d['foo'].append('foo')
>>> d
defaultdict(<type 'list'>, {'foo': ['foo']})
>>> d = defaultdict(set)
>>> d['foo'].add('foo')
>>> d['foo'].add('bar')
>>> d['foo'].add('foo')
>>> d
defaultdict(<type 'set'>, {'foo': set(['foo', 'bar'])}
Группировка элементов
Теперь все ясно ;)
result = {}
for index, element in enumerate(random_string):
result.setdefault(element, []).append(index)
Используем defaultdict
result = defaultdict(list)
for index, element in enumerate(random_string):
result[element].append(index)
Группировка элементов
Действительно, высокопроизводительный контейнер
Результаты timeit
Самый простой подход, (if key in result ...):
3.07053208351
С использованием setdefault:
3.91122484207
С использованием defaultdict:
2.63944602013
Про именование
Назови меня по имени
Знакомая картина?
# pure Python
elements = [
(subelement1, subelement2, ....),
(subelement11, subelement22, ....),
(subelement111, subelement222, ....),
(subelement1111, subelement2222, ....),
]
# Django
Model.objects.all().values_list('name', 'surname', 'age')
Назови меня по имени
Более "живой" пример
from mock import Mock
database = Mock()
database.make_query = Mock(return_value=[
('Book1', 'Author1', '10/01/2001'),
('Book2', 'Author2', '10/02/2002'),
('Book3', 'Author3', '10/03/2003'),
('Book4', 'Author4', '10/04/2004'),
('Book5', 'Author5', '10/05/2005')])
publications = data.make_query()
# pass publications as template context
Назови меня по имени
Дальше только хуже ...
{% for publication in publications %}
<div>publication[0]</div>
<span>publication[1]</span>
<b>publication[2]</b>
{% endfor %}
Назови меня по имени
Модуль collections!
namedtuple - фабричная функция для кортежей с именованными
полями.
Именованные кортежи позволяют определить имена для каждой
позиции в кортеже.
Использование именованных кортежей позволяет создавать более
читаемый и понятный код. Они могут быть использованы в тех же
случаях, что и обычные кортежи, а обращаться к полям можно не
только по индексу, но и по имени.
Именованные кортежи используют памяти не больше, чем
обычные кортежи.
new in Python v2.6
Назови меня по имени
Как оно работает
>>> from collections import namedtuple
>>> Person = namedtuple('Person', 'name surname age')
>>> person = Person(name='Max', surname='Usachev', age=25)
>>> person = Person('Max', 'Usachev', 25)
>>> person.name, person.surname, person.age
('Max', 'Usachev', 25)
>>> person[0], person[1], person[2]
('Max', 'Usachev', 25)
Назови меня по имени
Как оно работает еще
>>> person._asdict()
OrderedDict([('name', 'Max'), ('surname', 'Usachev'), ('age', 25)])
>>> Person.full_name = property(lambda self: ' '.join((self.name,
self.surname)))
>>> person.full_name
'Max Usachev'
>>> Person._make(('Max', 'Usachev', 25))
Person(name='Max', surname='Usachev', age=25)
>>> Person._fields
('name', 'surname', 'age')
Назови меня по имени
Дополненный начальный пример
from collections import namedtuple
...
Publication = namedtuple('Publication', 'name author date')
publications = map(Publication._make, database.make_query())
{% for publication in publications %}
<div>publication.name</div>
<span>publication.author</span>
<b>publication.date</b>
{% endfor %}
Про сортировку
sortируй правильно
Все та же randomная строка
Знаем что и как часто
# DON'T COUNT THIS WAY
letters = [(letter, random_string.count(letter)) for 
letter in set(random_string)]
Какой символ встречается чаще всего?
sortируй правильно
Самое распространенное решение
sorted(letters, key=lambda element: element[1])
sortируй правильно
Но все-таки лучше так:
sorted(letters, key=itemgetter(1))
И timeit уверен в этом!
С использованием lambda:
4.4638299942
С использованием itemgetter:
3.61604499817
sortируй правильно
Модуль operator
Модуль operator предлагает набор эффективных функций,
соответствующих внутренним операторам Python:
- add(a,b)
- div(a, b)
- not(a)
- ...
А также такие полезные функции, как:
- attrgetter(*attrs)
- itemgetter(*items)
- methodcaller(name[, args...]
sortируй правильно
Как работает operator.itemgetter
>>> from operator import itemgetter
>>> getter = itemgetter(0)
>>> getter('abc')
'a'
>>> getter([0, 1, 2])
0
>>> getter = itemgetter(0, -1)
>>> getter('abc')
('a', 'c')
>>> getter([1, 2, 3])
(1, 3)
new in Python v2.4
sortируй правильно
Как работает operator.attrgetter
>>> from operator import attrgetter
>>> getter = attrgetter('name')
>>> class Foo(object): pass
>>> foo = Foo()
>>> foo.name = 'foo'
>>> getter(foo)
'foo'
>>> foo.weight = 20
>>> getter = attrgetter('name', 'weight')
>>> getter(foo)
('foo', 20)
new in Python v2.4
Про фильтрацию
filtруй правильно
Часто нужно принять решение на основе содержимого списка,
а точнее, проверить, не пустой ли он
elements = [...]
condition = lambda x: ...
if len([element for element in elements if condition(element)]):
# do something
filtруй правильно
Для начала так:
if [element for element in elements if condition(element)]:
# do something
* Но список может быть большим
* А если нашли первый подходящий элемент, то зачем искать дальше?
Напрашивается решение на основе генераторов!
filtруй правильно
Вот оно!
if any(element for element in elements if condition(element)):
# do something
Т.е. any вернет True как только встретит первый трушный элемент
Внимание, вопрос: что тут не так?
filtруй правильно
Как работает any:
def any(iterable):
for element in iterable:
if element:
return True
return False
filtруй правильно
И если вдруг:
>>> nums = [0, 22, 15]
>>> condition = lambda x: x < 10
>>> any(num for num in nums if condition(num))
False
filtруй правильно
Решение:
>>> any(condition(num) for num in nums)
True
делай все правильно
* Пишите красивый код
* Пишите правильный код (cпрашивайте себя "Не ХХХXХ ерунду ли я
пишу?")
* Изучайте стандартную библиотеку Python
* Используйте collections, operator, itertools ...
* Приходите снова!

Красота и изящность стандартной библиотеки Python

  • 1.
    Красота и изящностьстандартной библиотеки Python Примеры использования Макс Усачев m_usachev@wargaming.net
  • 2.
  • 3.
    Группировка элементов Дана произвольнаястрока import string import random RANDOM_STRING_LENGTH = 999 choices = string.ascii_letters + string.digits random_string = [random.choice(choices) for i in xrange(RANDOM_STRING_LENGTH)] Найти позиции каждого уникального символа этой строки в исходной строке.
  • 4.
    Группировка элементов Первое, чтоприходит людям на ум и они так это и оставляют: result = {} for index in xrange(len(random_string)): element = random_string[index] if element in result.keys(): result[element].append(index) else: result[element] = [index]
  • 5.
    Группировка элементов После тревожнойночи иногда код становится чуть лучше: result = {} for index, element in enumerate(random_string): if element in result: result[element].append(index) else: result[element] = [index]
  • 6.
    Группировка элементов А потомты понимаешь... Что кода слишком много! result = {} for index, element in enumerate(random_string): result.setdefault(element, []).append(index)
  • 7.
    Группировка элементов Казалось бы,куда уже короче ... result = {} for index, element in enumerate(random_string): result.setdefault(element, []).append(index)
  • 8.
    Группировка элементов Модуль collections! Модуль collections реализует высокопроизводительные контейнерные типы данных. Counter, deque, OrderedDict и defaultdict, а также функция для создания типов данных - namedtuple(). Контейнеры, создаваемые при помощи этого модуля, представляют собой альтернативу встроенным в Python контейнерам общего назначения: dict, list, set и tuple. Документация: http://docs.python.org/2/library/collections.html#module-collections Исходный код: http://hg.python.org/cpython/file/2.7/Lib/collections.py http://hg.python.org/cpython/file/2.7/Lib/_abcoll.py
  • 9.
    Группировка элементов Примеры использованияdefaultdict Когда фабричная функция is None >>> from collections import defaultdict >>> d = defaultdict() >>> d['foo'] Traceback (most recent call last): File "<stdin>", line 1, in <module> KeyError: 'foo' new in Python v2.5 Если фабричная функция не указана, при обращении по несуществующему ключу ведет себя как обычный dict.
  • 10.
    Группировка элементов Примеры использованияdefaultdict Когда фабричная функция is not None >>> d = defaultdict(int) >>> d['foo'] 0 >>> d['foo'] += 1 >>> d defaultdict(<type 'int'>, {'foo': 1}) >>> d['foo1'] += 5 >>> d defaultdict(<type 'int'>, {'foo': 1, 'foo1': 5})
  • 11.
    Группировка элементов Примеры использованияdefaultdict Когда фабричная функция is not None >>> d = defaultdict(list) >>> d['foo'].append('foo') >>> d defaultdict(<type 'list'>, {'foo': ['foo']}) >>> d = defaultdict(set) >>> d['foo'].add('foo') >>> d['foo'].add('bar') >>> d['foo'].add('foo') >>> d defaultdict(<type 'set'>, {'foo': set(['foo', 'bar'])}
  • 12.
    Группировка элементов Теперь всеясно ;) result = {} for index, element in enumerate(random_string): result.setdefault(element, []).append(index) Используем defaultdict result = defaultdict(list) for index, element in enumerate(random_string): result[element].append(index)
  • 13.
    Группировка элементов Действительно, высокопроизводительныйконтейнер Результаты timeit Самый простой подход, (if key in result ...): 3.07053208351 С использованием setdefault: 3.91122484207 С использованием defaultdict: 2.63944602013
  • 14.
  • 15.
    Назови меня поимени Знакомая картина? # pure Python elements = [ (subelement1, subelement2, ....), (subelement11, subelement22, ....), (subelement111, subelement222, ....), (subelement1111, subelement2222, ....), ] # Django Model.objects.all().values_list('name', 'surname', 'age')
  • 16.
    Назови меня поимени Более "живой" пример from mock import Mock database = Mock() database.make_query = Mock(return_value=[ ('Book1', 'Author1', '10/01/2001'), ('Book2', 'Author2', '10/02/2002'), ('Book3', 'Author3', '10/03/2003'), ('Book4', 'Author4', '10/04/2004'), ('Book5', 'Author5', '10/05/2005')]) publications = data.make_query() # pass publications as template context
  • 17.
    Назови меня поимени Дальше только хуже ... {% for publication in publications %} <div>publication[0]</div> <span>publication[1]</span> <b>publication[2]</b> {% endfor %}
  • 18.
    Назови меня поимени Модуль collections! namedtuple - фабричная функция для кортежей с именованными полями. Именованные кортежи позволяют определить имена для каждой позиции в кортеже. Использование именованных кортежей позволяет создавать более читаемый и понятный код. Они могут быть использованы в тех же случаях, что и обычные кортежи, а обращаться к полям можно не только по индексу, но и по имени. Именованные кортежи используют памяти не больше, чем обычные кортежи. new in Python v2.6
  • 19.
    Назови меня поимени Как оно работает >>> from collections import namedtuple >>> Person = namedtuple('Person', 'name surname age') >>> person = Person(name='Max', surname='Usachev', age=25) >>> person = Person('Max', 'Usachev', 25) >>> person.name, person.surname, person.age ('Max', 'Usachev', 25) >>> person[0], person[1], person[2] ('Max', 'Usachev', 25)
  • 20.
    Назови меня поимени Как оно работает еще >>> person._asdict() OrderedDict([('name', 'Max'), ('surname', 'Usachev'), ('age', 25)]) >>> Person.full_name = property(lambda self: ' '.join((self.name, self.surname))) >>> person.full_name 'Max Usachev' >>> Person._make(('Max', 'Usachev', 25)) Person(name='Max', surname='Usachev', age=25) >>> Person._fields ('name', 'surname', 'age')
  • 21.
    Назови меня поимени Дополненный начальный пример from collections import namedtuple ... Publication = namedtuple('Publication', 'name author date') publications = map(Publication._make, database.make_query()) {% for publication in publications %} <div>publication.name</div> <span>publication.author</span> <b>publication.date</b> {% endfor %}
  • 22.
  • 23.
    sortируй правильно Все таже randomная строка Знаем что и как часто # DON'T COUNT THIS WAY letters = [(letter, random_string.count(letter)) for letter in set(random_string)] Какой символ встречается чаще всего?
  • 24.
    sortируй правильно Самое распространенноерешение sorted(letters, key=lambda element: element[1])
  • 25.
    sortируй правильно Но все-такилучше так: sorted(letters, key=itemgetter(1)) И timeit уверен в этом! С использованием lambda: 4.4638299942 С использованием itemgetter: 3.61604499817
  • 26.
    sortируй правильно Модуль operator Модульoperator предлагает набор эффективных функций, соответствующих внутренним операторам Python: - add(a,b) - div(a, b) - not(a) - ... А также такие полезные функции, как: - attrgetter(*attrs) - itemgetter(*items) - methodcaller(name[, args...]
  • 27.
    sortируй правильно Как работаетoperator.itemgetter >>> from operator import itemgetter >>> getter = itemgetter(0) >>> getter('abc') 'a' >>> getter([0, 1, 2]) 0 >>> getter = itemgetter(0, -1) >>> getter('abc') ('a', 'c') >>> getter([1, 2, 3]) (1, 3) new in Python v2.4
  • 28.
    sortируй правильно Как работаетoperator.attrgetter >>> from operator import attrgetter >>> getter = attrgetter('name') >>> class Foo(object): pass >>> foo = Foo() >>> foo.name = 'foo' >>> getter(foo) 'foo' >>> foo.weight = 20 >>> getter = attrgetter('name', 'weight') >>> getter(foo) ('foo', 20) new in Python v2.4
  • 29.
  • 30.
    filtруй правильно Часто нужнопринять решение на основе содержимого списка, а точнее, проверить, не пустой ли он elements = [...] condition = lambda x: ... if len([element for element in elements if condition(element)]): # do something
  • 31.
    filtруй правильно Для началатак: if [element for element in elements if condition(element)]: # do something * Но список может быть большим * А если нашли первый подходящий элемент, то зачем искать дальше? Напрашивается решение на основе генераторов!
  • 32.
    filtруй правильно Вот оно! ifany(element for element in elements if condition(element)): # do something Т.е. any вернет True как только встретит первый трушный элемент Внимание, вопрос: что тут не так?
  • 33.
    filtруй правильно Как работаетany: def any(iterable): for element in iterable: if element: return True return False
  • 34.
    filtруй правильно И есливдруг: >>> nums = [0, 22, 15] >>> condition = lambda x: x < 10 >>> any(num for num in nums if condition(num)) False
  • 35.
  • 36.
    делай все правильно *Пишите красивый код * Пишите правильный код (cпрашивайте себя "Не ХХХXХ ерунду ли я пишу?") * Изучайте стандартную библиотеку Python * Используйте collections, operator, itertools ... * Приходите снова!