Посты с тэгом tips and tricks


Fast Python. Выпуск 1. Обновление словарей

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

Первый выпуск будет посвящен обновлению данных в словарях. Словари - одни из найболее часто используемых типов данных в Python. И сколько времени я с ними не работаю, у меня никогда не возникал вопрос как правильней обновить данные в словаре. Я для себя всегда отвечал на него, что правильней обновлять используя метод update, но сегодня с утра наткнулся на твит от Брэда Монтгомери и мой мир изменился.

Оказывается, что более правильным с точки зрения скорости является обновление словаря через по



Upgrade your pip & virtualenv now

Странно, что несмотря на очень давний выход pip 6.0 и virtualenv 12.0, многие Python разработчики все еще сидят на более ранних версиях этих незаменимых утилит.

Мой вам совет - обновляйте свой pip & virtualenv сейчас же!

Главная причина - это, конечно, встроенный в pip, толковый и включенный по умолчанию менеджер скачанных зависимостей. Да, и раньше можно было пользоваться опцией --download-cache или конфигом:

[install]
download_cache = /path/to/pip-cache

в ~/.pip/pip.conf, но старый менеджер загрузок был скорее дополнительным, чем полностью готовым к использованию механизмом. Более детальный ход разработки менеджера загрузок хорошо продемонстрирован на GitHub.

Из остального



Range для не целых чисел

Почему-то никогда не думал, что rangexrange) понимают только целые числа (в Python 3 ситуация такая же) и функцией нельзя воспользоваться, как например:

range(0., 5., .5)
range(Decimal('0'), Decimal('10'), Decimal('1.5'))

Поэтому пришлось сделать свою замену:

import operator


def arange(start, stop=None, step=None):
    """
    Implement range function not only for integers as Python's builtin
    function, but for Decimals and floats as well.

    Returns generator with arithmetic progession, not list.
    """
    klass = type(start)
    lt_func = operator.lt

    stop = start if stop is None else stop
    start = klass(0) if start == stop else start
    step = klass(1 if step is None else step)

    assert isinstance(stop, klass), (
        'Start and stop limits have different types, {0!


Запускаем gunicorn из Python'а

Иногда бывает надо запустить gunicorn внутри Python скрипта, например, в manage.py. Конечно всегда можно воспользоваться subprocess.call:

import subprocess


app = 'package.module:app'
host, port = '0.0.0.0', 8000
subprocess.call('gunicorn -b {}:{:d} -w 4 {}'.format(host, port, app))

Но как-то это не комильфо подумал я и решил найти более труЪ-способ :)

Решение пришло не сразу, но пришло, надо всего лишь переопределить sys.argv и вызвать метод run,

import sys

from gunicorn.app.wsgiapp import run


app = 'package.module:app'
host, port = '0.0.0.0', 8000
sys.argv = [
    sys.argv[0],
    '-b', '{}:{:d}'.format(host, port),
    '-w', '4',
    app
]
run()

Не очень круто вышло, не находите? А



Чиним gunicorn'овский Internal Server Error

Если вдруг, после запуска gunicorn перестал работать и показывает ошибку похожую на:

Internal Server Error

Traceback:

Traceback (most recent call last):
  File "/Users/playpauseandstop/Projects/project/env/lib/python2.7/site-packages/gunicorn/workers/async.py", line 45, in handle
    self.handle_request(req, client, addr)
  File "/Users/playpauseandstop/Projects/project/env/lib/python2.7/site-packages/gunicorn/workers/async.py", line 73, in handle_request
    resp, environ = wsgi.create(req, sock, addr, self.address, self.cfg)
  File "/Users/playpauseandstop/Projects/project/env/lib/python2.7/site-packages/gunicorn/http/wsgi.py", line 161, in create
    path_info = path_info.split(script_name, 1)[1]
IndexError: list index out of range

то это говорит о том, что какая-то сволочьое-то приложение установило переменную окружения SCRIPT_NAME, которую gunicorn



Копируем виртуальные окружения с помощью virtualenv-clone

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

Для начала еще раз поясню саму идею. Есть репозиторий, есть деплоймент сервис, который для каждого коммита может генерировать код/базы данных/бутстрап проекта/что-угодно и выдавать на гора результат в виде готового для доступа URL-адреса. Для вытаскивания кода и расположения его в определенной директории могут использоваться разные подходы, как впрочем и для работы с базой данных. Но бутстрап проекта для уже готовой базы данных хочется сделать просто:

$ virtualenv --distribute --system-site-packages env
$ . env/bin/activate
(env)$ pip install -r requirements.txt
(env)$ deactivate
$ cp project/settings_local.py{.deploy,}

И вроде бы все работает, но чем больше стано



Постоянные сессии во Flask'е, один из способов

По умолчанию, все содержимое flask.session будет очищено при закрытии браузера. Однако много когда нам нужно, чтоб данные сессии хранились и после рестарта браузера. Для этих случаев есть аттрибут permanent и следующий простой сниппет:

import datetime

from flask import Flask, session


app = Flask(__name__)
app.before_request(lambda: setattr(session, 'permanent', True))
app.permanent_session_lifetime = datetime.timedelta(days=14)

Последняя строчка сниппета выставляет длину сессии в 14 дней, во Flask'е же по дефолту используется 31 день для хранения постоянной сессии. Также эту настройку можно указать как PERMANENT_SESSION_LIFETIME в вашем settings.py.

зы. Однако также не забывайте, что Flask хранит все данные сессии в кукисах, а не



Один маленький совет для ускорения бутстрапа проектов

Конечно, использование git:// урлов в файле зависимостей проекта не есть отличная идея, но если вы таки решились на нее и даже делаете как-то так:

-e git://github.com/user/repo.git@commit#egg=package

то я спешу вас облагоразумить! Не делайте так! НИКОГДА :)

В случае в репозиториями GitHub'а, используйте zipball'ы (tarball'ы нормально не распознаются pip),

https://github.com/user/repo/zipball/commit#egg=package

в других случаях просто архивируйте при помощи git archive необходимый вам коммит или тег, и грузите его на свой cdn. И теперь вам не надо будет ожидать пока пип склонирует репо и поставит хедом необходимый вам коммит. Ускорение бутстрапа на жирных зависимостях (например, ask/celery, django/django будет очень ощутимым)!

зы. И да, даже для мастера (любого другого бранча) вам не нужно использовать git:// в случае GitHub'а. Используе



Соблюдаем порядок секций при работе с конфиг файлом

Столкнулся с весьма забавной проблемой при работе с конфиг файлами, используя стандартную библиотеку ConfigParser. Дело в том, что до версии 2.7 эта библиотека не соблюдает порядок в котором расположены секции в конфиг файле, потому что хранит их в обыкновенном dict объекте. В версии же 2.7 библиотека использует добавленный в collections класс OrderedDict и никаких проблем с порядком нет.

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

Решение весьма простое, использовать dict_type при инициализации парсера, например так:

from ConfigParser import SafeConfigParser

from django.utils.datastructures 


django.dispatch.receiver - FTW!

Думаю, долгое время, каждый из нас присоединял сигналы к событиям при помощи старого доброго connect метода, например как:

from django.contrib.auth.models import User
from django.db.models import signals

...

signals.post_save.connect(auto_create_user_profile, sender=User)

Однако с выходом Django 1.3 ситуация поменялась. Сейчас достаточно задекорировать функцию сигнала, в нашем случае auto_create_user_profile, с помощью @receiver декоратора:

from django.contrib.auth.models import User
from django.dispatch import receiver

...

@receiver(signals.post_save, sender=User)
def auto_create_user_profile(instnance, **kwargs):
    ...

И все, готово! Согласитесь, удобней и легче чем раньше.

зы. Удачного рефакторинга! ;)