Показаны сообщения с ярлыком python. Показать все сообщения
Показаны сообщения с ярлыком python. Показать все сообщения

2016-01-29

Python NLTK + Stanford NLP

Как известно, в Python стандартом работы с натуральным языком де-факто является NLTK. Несмотря на это, я довольно долго использовал Pattern от CLiPS из-за его простоты и скорости (многие отмечают тормознутость NLTK).

Но наступил момент, когда почти вся кодовая база была успешно портирована на Python 3.5, а разработчики Pattern так и не сделали версию с поддержкой третьей версии. И, судя по всему, не собираются.

Что-ж, будем использовать NLTK. От него мне нужны: токенизация, выделение POS (part-of-speech), получение N-grams и классификация твитов на группы с использованием Naive Bayes. Все это дело на Python 3.5.


pip3 install nltk

Модули для NLTK устанавливаются так:


import nltk
nltk.download()

... или так:


python3 -m nltk.downloader punkt averaged_perceptron_tagger
# python3 -m nltk.downloader all

Теперь немного примеров.


tweet = '''
    Kinto by Mozilla - An open source Parse alternative >>
    https://github.com/Kinto/kinto/ #python #parse
'''

# Получаем токены. Стандартный универсальный метод:
from nltk import word_tokenize
print(word_tokenize(tweet))
'''
[
    'Kinto', 'by', 'Mozilla', '-', 'An', 'open', 'source',
    'Parse', 'alternative','>', '>', 'https', ':',
    '//github.com/Kinto/kinto/', '#', 'python', '#', 'parse'
]
'''

# С использованием специализированного класса
from nltk.tokenize import TweetTokenizer
# Убрать имена пользователей и многократно повторяемые символы
tt = TweetTokenizer(preserve_case=True, reduce_len=False, strip_handles=False)
print(tt.tokenize(tweet))
'''
[
    'Kinto', 'by', 'Mozilla', '-', 'An', 'open',
    'source', 'Parse', 'alternative', '>', '>',
    'https://github.com/Kinto/kinto/', '#python', '#parse'
]
'''

# Возьмем немного почищеные токены. Воспользуемся обычным токенизатором
tokens = [
    t for t in word_tokenize(tweet)
        if len(t) > 1 and not t.startswith('http')
        and not t.startswith('/')
]
'''
[
    'Kinto', 'by', 'Mozilla', 'An', 'open', 'source',
    'Parse', 'alternative', 'python', 'parse'
]
'''

Токены получили, теперь будем узнавать части речи. Можно использовать встроенный метод, а можно пойти интересным путем, и привлечь Stanford NLP библиотеки. Для этого скачаем и распакуем каталог с ними в нашу рабочую директорию, где пишем код. В Python укажем место расположения JAR и модулей через переменные окружения.


import os
from nltk.tag import StanfordPOSTagger
os.environ['CLASSPATH'] = os.path.join(
    os.path.curdir, 'stanford-postagger-2015-04-20'
)
os.environ['STANFORD_MODELS'] = os.path.join(
    os.path.curdir, 'stanford-postagger-2015-04-20', 'models'
)
stanford_tagger = StanfordPOSTagger('english-bidirectional-distsim.tagger')

Теперь можно сравнить результаты методов:


from nltk import pos_tag
print(pos_tag(tokens))
print(stanford_tagger.tag(tokens))

Результаты почти идентичны, за исключением "An". StanfordPOSTagger считает его существительным. Допустим, мы выберем последний вариант. Найдем все имена существительные (NN, NNS, NNP, NNP-PERS, NNP-ORG):


tagger = stanford_tagger.tag
nouns = [word.lower() for word, pos in tagger(tokens) if pos.startswith('NN')]
print(nouns)  # ['kinto', 'mozilla', 'an', 'source', 'parse', 'python', 'parse']

Теперь N-grams. Не проблема сделать самостоятельно, но в NLTK уже есть пара готовых функций.


from nltk.util import everygrams, ngrams
print(list(ngrams(nouns, 2)))
# [('kinto', 'mozilla'), ('mozilla', 'an'), ...  ('python', 'parse')]
print(list(everygrams(nouns, min_len=2, max_len=3)))
'''
[
    ('kinto', 'mozilla'), ('mozilla', 'an'), ... ('python', 'parse'),
    ('kinto', 'mozilla', 'an'), ('mozilla', 'an', 'source'),
    ... ('parse', 'python', 'parse')
]
'''

Как я уже говорил, NLTK порой очень медленный. С использованием Stanford библиотек он медленнее в разы. Существенно улучшает ситуацию обработка больших объемов твитов разом, а не один за другим.


from nltk import pos_tag, pos_tag_sents
tokens_group = [tokens for i in range(100)]
print(pos_tag_sents(tokens_group))  # 0.3 sec
print(stanford_tagger.tag_sents(tokens_group))  # 6.4 sec !!

На сём откланиваюсь.

2015-11-26

Python 3.5 + Docker

Один из самых удобных и современных способов деплоя рабочего окружения является Docker. В предыдущих постах мы устанавливали свежий Python из исходников, но если у вас несколько серверов, да еще и с разными версиями ОС, то этот процесс может отличаться. К тому же это безумие в чистом виде. Как вариант предлагают использование virtualenv, но он тоже не чистая песочница, и использует часть библиотек окружения, что может иметь последствия. Короче, Docker - идеальная изоляция не в ущерб производительности, не зависящая от версии ОС и установленных в ней библиотек. На DockerHub уже есть готовые образы, часть из них официальные, часть от частных лиц. Мы возьмем официальный Python 3.5 и добавим нужные нам пакеты. Минимум телодвижений - и набор готов.

Будем использовать скрипт автоматизации, который называется Dockerfile. Для создания нужного образа достаточно будет выполнить команду в каталоге с Dockerfile:


docker build -t python35 .

А вот примерное содержимое этого конфигурационного файла:


FROM python:3.5
MAINTAINER Main Tainer maintainer@email.com

# Update and install needed packaged:
# libblas-dev and liblapack-dev for matplotlib, gfortran for scipy
RUN apt-get -y update && apt-get install -y libblas-dev liblapack-dev gfortran

# Install packages for DataScience
RUN pip3 install numpy scipy sklearn pandas matplotlib seaborn nltk ipython[all] jupyter

# Web frameworks
RUN pip3 install tornado flask flask-admin flask-login flask-restful
RUN pip3 install django django-bootstrap3 django-admin-bootstrapped

# Databases: psycopg2 (Postgres), pika (RabbitMQ), sqlalchemy (ORM)
RUN pip3 install psycopg2 sqlalchemy pymongo pika redis elasticsearch

# HTTP-requests http://docs.python-requests.org/en/latest/
RUN pip3 install requests requests_oauthlib

# Image manipulations https://pillow.readthedocs.org/en/3.0.x/
RUN pip3 install pillow

# Make C-modules http://cython.org/
RUN pip3 install cython

# Scheduler https://apscheduler.readthedocs.org/en/latest/
RUN pip3 install apscheduler

Вот и все, можно запускать!


docker run -ti python35 python

Python 3.5.0 (default, Nov 20 2015, 06:18:32)
[GCC 4.9.2] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>>

Что-нибудь посодержательнее? Ок, прокинем в контейнер каталог с реальным кодом, например с реализацией алгоритма для вычисления количества шестизначных счастливых билетов, и выполним скрипт:


docker run -v /path/outside:/path/inside python35 python /path/inside/code.py

Ура, теперь то мы знаем, что их всего 55252.

2015-11-24

Python3.5 + PyQt5 - The Hard Way

Сегодня мы, в продолжение предыдущей статьи, установим библиотеку PyQt5 для собранного из исходников Python3.5. Для версии Python3, идущей в репозитории дистрибутива есть простой путь, и в общем случае лучше придерживаться его - он проще и дает аналогичный результат.


sudo apt-get install python3-pyqt5 pyqt5-dev-tools

Маководам тоже повезло, им также хватит одной строки (если стоит Homebrew, а куда ж без него?):


brew install pyqt5

Тех же, кто не ищет легких путей или кому нужна именно собственноручно собранная версия, милости прошу к прочтению.

Итак, поехали. Для начала, скачиваем подходящий установщик Qt5, мне нужен был для Linux x64, запускаем и устанавливаем, можно в каталог по умолчанию ~/Qt.

Качаем и распаковываем свежие версии PyQt5 и sip отсюда: http://sourceforge.net/projects/pyqt/files/. Помним, что мы установили python в локальный каталог ~/local. В каталоге с распакованным sip конфигурируем и устанавливаем:


python3 configure.py -d ~/local/lib/python3.5/site-packages/
make
make install

Для успешной сборки GUI модулей PyQt5 (типа PyQt5.QtWidgets) пришлось установить пакетик с исходниками mesa:


sudo apt-get install libgl1-mesa-dev

Теперь в каталоге с распакованным PyQt5 конфигурируем и устанавливаем:


python3 configure.py --destdir ~/local/lib/python3.5/site-packages/\
 --qmake ~/Qt/5.5/gcc_64/bin/qmake --disable QtPositioning
make
make install

Почему здесь фигурирует "--disable QtPositioning"? Потому что иначе не собирается, выбрасывая сообщение "qgeolocation.h: No such file or directory". Да и шут с ним. Вот собственно и всё, можно начинать писать код.


from PyQt5.QtWidgets import QApplication, QWidget
app = QApplication([])
w = QWidget()
w.resize(300, 200)
w.move(400, 400)
w.setWindowTitle('PyQt5 installed')
w.show()

Получите, распишитесь.

2015-11-19

Setup Python3.5 for DataScience

Цель - установить локальную версию свежего дистрибутива Python 3.5 и группу пакетов, полезных для ковыряния в данных. Платформа - Linux Mint 17.2 x64. Устанавливаем пакет с компиляторами на все случаи жизни, скачиваем с официального сайта архив с исходниками и распаковываем его, скажем, в каталог ~/Python-3.5.0. В терминале перемещаемся в него и начинаем.


sudo apt-get install build-essential git git-core xz-utils
wget https://www.python.org/ftp/python/3.5.0/Python-3.5.0.tar.xz
tar -xpJf Python-3.5.0.tar.xz
cd Python-3.5.0

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


sudo apt-get install tk-dev libsqlite3-dev libbz2-dev libfreetype6-dev
sudo apt-get install liblzma-dev libgdmb-dev libreadline-dev
sudo apt-get install lib32ncurses5-dev libpng12-dev libjpeg-dev tk
sudo apt-get install liblapack-dev libplas-dev gfortran libpq-dev

В среде Google Cloud в Ubuntu 15.04 пакеты libgdmb-dev и libplas-dev не находятся, возможно есть замена, но я не разбирался, не очень то и нужны.

Теперь собственно, сборка и проверка. Предварительно создадим каталог для локальной версии Python, у меня это ~/local.


./configure --prefix=$HOME/local
make -j4
make test
make install

У меня не установился модуль _ctypes, и непонятно, чего ему не хватило. Если знаете, в чем дело, напишите в комментариях. Стоит добавить в файл ~/.bashrc несколько строк, чтобы оболочка была в курсе, где наш новый Python и его библиотеки живут.


export PATH=$HOME/local/bin:$PATH
export LD_LIBRARY_PATH=$HOME/local/lib:$LD_LIBRARY_PATH
export C_INCLUDE_PATH=$HOME/local/include/:$C_INCLUDE_PATH

Теперь настало время для наших изыскательских (и просто ежедневно полезных) пакетов. Первая строка - mast have для исследователя, остальные - не обязательно, они просто хорошие :)


pip3 install numpy scipy sklearn pandas matplotlib seaborn ipython[all] jupyter
pip3 install tornado pillow psycopg2 sqlalchemy cython nltk
pip3 install requests requests_oauthlib elasticsearch
pip3 install django django-bootstrap3 django-admin-bootstrapped
pip3 install flask flask-admin flask-login apscheduler pymongo pika redis

С matplotlib могут возникнуть сложности, а именно - без выбрасывания ошибок не показывает окно с результатом. Чтобы этого не происходило мы и установили tk и собрали Python с tk-dev. Стоит проверить, что в файле с настройками matplotlib (у меня это '~/local/lib/python3.5/site-packages/matplotlib/mpl-data/matplotlibrc') выставлен верный параметр backend: TkAgg. Можно выставить его и прямо в коде. Также, можно попробовать установить matplotlib с github:


pip3 install git+https://github.com/matplotlib/matplotlib.git

Кроме того, при сборке matplotlib может ругнуться на отсутствие freetype, даже если установлен libfreetype6-dev. Такое я наблюдал, когда собирал Docker контейнер с основой на ubuntu:trusty. Помогло создание ссылки:


ln -s /usr/include/freetype2/ft2build.h /usr/include/

import numpy as np
import matplotlib.pyplot as plt
import matplotlib

matplotlib.use("TkAgg")
plt.imshow(np.random.random((10, 10)))
plt.show()

Красивая калякамаляка получилась!

2015-11-13

Custom Search Engine + Python

Использование Custom Search Engine API от Google из Python мне показалось несколько запутанным, поэтому опишу это пошагово, возможно кому-нибудь пригодится. Для начала, нам понадобится библиотека API Client Library for Python, установить которую не представляет труда:


sudo pip install --upgrade google-api-python-client

Во вторых, нам будет нужен идентификатор CSE, который мы для себя создадим на официальной странице. Добавьте в список сайтов для поиска "google.com", а переключатель "Поиск изображений" установите в положение ВКЛ. После того, как сохраним результаты, нажатие кнопки "Идентификатор поисковой системы" покажет нам то, для чего мы прошли все эти мытарства. Это будет похоже на "01345678901234567890:qwer23tyu_i".

Теперь отправляемся в консоль разработчика, а именно в раздел "APIs & auth/APIs". Найдите в списке "Custom Search API" и подключите его.

Там же в консоли переходим в пункт "Credentials", жмем "Add credntials", выбираем "API Key", затем жмем "Server key", даем ему имя и сохраняем. Забираем ключик, он будет похож на "QWerTyQWerTyQWe-QWerTyQWerTyQWerTyQWerTy".

На этом с ключами всё, пишем код на Python. В примере мы получаем все уникальные изображения пляжа, которые может позволить этот API:


from apiclient.discovery import build

service = build('customsearch', 'v1', developerKey="QWerTyQWerTyQWe-QWerTyQWerTyQWerTyQWerTy")

start = 1

while True:

    res = service.cse().list(
        q='beach',
        cx='01345678901234567890:qwer23tyu_i',
        fileType="png,jpg",  # bmp, gif, png, jpg, svg, pdf, ...
        imgColorType="color",  # mono, gray, color
        imgSize="medium",  # icon, small, medium, large, xlarge, xxlarge, huge
        imgType="photo",  # clipart, face, lineart, news, photo
        safe="high",  # high, medium, off
        searchType="image",
        start=start
    ).execute()

    for img in res["items"]:
        print(img["link"])

    if "nextPage" not in res["queries"]:
        break

    start = res["queries"]["nextPage"][0]["startIndex"]

2015-07-17

Быстрая переброска данных в удаленный Postgres

Передо мной стояла задача быстрой вставки большого количества JSON в Postgres на удаленном сервере. Обычное соединение к СУБД и использование "INSERT" нужной скорости не давало, даже с "executemany". Решением стал комплекс действий, состоящий из следующих этапов.

Первое - это компактификация пересылаемых данных (в моем случае это JSON). Для этого я использовал модуль dict_tools, который заменяет ключи в словаре с данными на уникальные и максимально короткие, возвращая словарь с заменами, который потом можно использовать для возвращения первоначальных имен.


from dict_tools import names_generator, names_replace
aliases = names_generator()
tweets = [{'text': 'Hello, world'}, {'text': 'Bye, chaos'}]
shorten, replaced = names_replace(tweets, aliases=aliases)

Кроме того, превращая словарь в JSON-строку использовал опции "separators", "encoding" и "ensure_ascii" - меньше пробелов и нормальные символы в UTF-8 вместо escape-последовательностей типа \u01010 для юникода:


def dumps(d):
    return json.dumps(d, separators=(',',':'), encoding='utf-8', ensure_ascii=False)

Второе - это отправка сжатых данных на сервер, где расположен Postgres. Для этого с одной стороны работает скрипт, использующий модуль requests и отправляющий данные. В словаре с замененными именами ключи и значения меняются местами, чтобы произвести обратную замену на принимающей стороне. Здесь "localhost:8888" просто для примера.


import requests
data = {
    'snames': dumps({v: k for k, v in replaced.items()}),
    'tweets': dumps(shorten)
}
r = requests.post('http://localhost:8888', data=data)
print(r.text)

На другой стороне висит минисервис на Tornado, принимающий присылаемые данные, возвращающий оригинальные ключи в JSON, размещающий данные построчно в CSV-файле и вызывающий команду Postgres "COPY".


import tornado.ioloop
import tornado.web
import json
import csv
import os
import psycopg2
from dict_tools import names_replace

class MainHandler(tornado.web.RequestHandler):
    def post(self, *args, **kwargs):

        tweets = json.loads(self.get_argument('tweets'))
        snames = json.loads(self.get_argument('snames'))
        tweets, replaced_names =  names_replace(tweets, repl=snames)

        path = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'dump.csv')

        fieldnames = ['id', 'src']
        with open(path, 'w') as csvfile:
            writer = csv.DictWriter(csvfile, fieldnames=fieldnames)
            writer.writeheader()
            for tweet in tweets:
                writer.writerow({
                    'id': tweet['id'],
                    'src': json.dumps(tweet, separators=(',',':'), encoding='utf-8', ensure_ascii=False).encode('utf-8')
                })

        con = psycopg2.connect(host='localhost', port=5432, user='XXX', password='XXX', database='XXX')
        cur = con.cursor()
        cur.execute("COPY dump_table(id, src) FROM '{}' WITH CSV HEADER".format(path))
        con.commit()
        con.close()

        self.write(u'Saved {} items'.format(len(tweets)))

if __name__ == "__main__":
    application = tornado.web.Application([(r"/", MainHandler)])
    application.listen(8888)
    tornado.ioloop.IOLoop.instance().start()

Такая схема у меня работает в несколько раз быстрее, чем вставка с удаленного сервера.

2015-07-15

Postgres + Python = Awesome

Чем больше работаю с Postgres, тем больше радует меня эта СУБД. Шикарная работа с JSON, позволяющая хранить структуры разнотипных данных в полях JSON и JSONB, и даже вешать на них индексы для быстрого поиска внутри них - находка для архивирования твитов и им подобных. Отличный набор функций для обработки текста, включая regexp. Типы-массивы (тоже с индексами). Переменных-таблиц нет, но их с успехом заменяют переменные-массивы или временные таблицы. Вот некоторые выражения, приводящие меня в щенячий восторг:


    -- Новая таблица по образцу выборки
    CREATE TABLE new_table AS SELECT field1, field2 FROM old_table LIMIT 10;

    -- Временная самоликвидирующаяся таблица
    CREATE TEMP TABLE IF NOT EXISTS new_table(id INT, data JSON) ON COMMIT DROP;

    -- Одно и то-же, во втором случае используется массив
    SELECT * FROM old_table WHERE id IN (1,2,3);
    SELECT * FROM old_table WHERE id = ANY('{1,2,3}');

    -- Создание массива в процессе группировки
    SELECT array_agg(text_id) FROM old_table WHERE id IN (1,2,3);

    -- JSON-массив JSON-объектов
    SELECT json_agg(t.jobj) FROM (
        SELECT json_build_object('text_id', id, 'record_id', _id) jobj
        FROM old_table
    ) t;

    -- Cоотношение твитов с вложенными чувствами и черствых:
    -- на выходе что-то вроде "{ "false" : 9115, "true" : 166 }"
    SELECT json_object_agg(t.ps, t.c) FROM (
        SELECT src#>>'{possibly_sensitive}' ps, COUNT(_id) c
        FROM tweet_archive
        WHERE src#>>'{possibly_sensitive}' = ANY('{false,true}') -- а тут массив с текстами
        GROUP BY src#>>'{possibly_sensitive}'
    ) t;

Для работы с Postgres используется Python и стандартная библиотека json. Для компактности и корректной работы с не-ASCII символами внутри JSON-полей Postgres при упаковке твитов используются такие параметры:

    json.dumps(tweet, separators=(',',':'), encoding='utf-8', ensure_ascii=False)

Еще одна возможность, которая может пригодиться - создание процедур в Postgres, используя Python. Думаю, примера будет достаточно, чтобы понять, как это делается (конечно официальной документации никто не отменял):


    CREATE OR REPLACE FUNCTION outer_procedure(p_limit INT) RETURNS VOID AS $$
        # Добавление пути для импорта нашего модуля
        import sys
        sys.path.insert(0, '/home/developer/plpython_func/')

        # Загрузка модуля
        import test_functions

        # На время разработки, пока модуль будет меняться,
        # его нужно насильно перезагружать, т.к. он кешируется
        reload(test_functions)

        # Собственно, запуск функции из модуля.
        # plpy - это объект для взаимодействия с БД
        test_functions.run(plpy, p_limit)

        return None
    $$ LANGUAGE plpythonu VOLATILE STRICT;

Внешняя процедура на Python (test_functions.py), которую мы загружаем:


    # -*- coding: utf-8 -*-

    import traceback

    def run(plpy, limit):

        # Готовим план выполнения
        sql = 'INSERT INTO new_table(id, text) VALUES($1, $2)'
        plan = plpy.prepare(sql, ["int", "text"])

        # Выполняем вставку внутри транзакции, ловим ошибки
        try:
            with plpy.subtransaction():
                plpy.execute(plan, [1, "text"])
        except plpy.SPIError as e:
            plpy.error("Error inside transaction: %s" % e.args)
        except:
            plpy.error(traceback.format_exc(10))

2014-10-16

Python: how to start independent process

Задача: запустить полностью независимый процесс из Python. Попробуем стартовать в Linux простой HTTP сервер и отправить его в свободное плавание:

import subprocess
subprocess.Popen(['python', '-m', 'SimpleHTTPServer', '5000'], close_fds=True)
Однако параметр "close_fds" в Windows не работает: "Note that on Windows, you cannot set close_fds to true". Как вариант, можно использовать стандартные компоненты Windows, cmd.exe и команду start:

import subprocess
subprocess.Popen([‘C:\Windows\System32\cmd.exe’, ‘/C’, ’start’, ‘C:\Python27\Python.exe’, ‘-m’, ’SimpleHTTPServer’, ‘5000’])
Это создаст новое окно cmd.exe, где будет запущен http-сервер. Что и требовалось.

2014-08-13

Python: how to stop process correctly

Одной из задач, с которой встречаются разработчики многопоточных/многопроцессных приложений, является их корректная остановка. Особенно это касается таких потоков/процессов, в которых осуществляется бесконечная блокирующая операция, типа цикла "while True" или чтения из некоего генератора.

while True:
    # do something
С потоками проще, потому что им можно передать ссылку на объект, который можно проверять при каждой итерации и останавливать при соблюдении условий.

import time
from threading import Thread

def in_thread(con):
    while con[0]:
        # ... doing something
        pass

con = [True]
t = Thread(target=in_thread, args=(con,))
t.start()

time.sleep(3)
# Stop the thread
con[0] = False
t.join()
С процессами несколько иначе, они более изолированные, и нужно использовать объекты синхронизации. Очень удобный способ мне подсказал коллега, с использованием Lock.

import time
from threading import Thread
from multiprocessing import Process, Lock

def in_process(lock):

    def in_thread(con):
        while con[0]:
            pass
            # ... doing something

    con = [True]
    t = Thread(target=in_thread, args=(con,))
    t.daemon = True
    t.start()

    # Lock acquired in parent process, child process waits, but thread already ran!
    lock.acquire()

    # Polite way
    con[0] = False
    t.join()

    # Way for impatient
    # import os
    # os._exit(0)


lock = Lock()
lock.acquire()
Process(target=in_process, args=(lock,)).start()

time.sleep(3)
# ... process in action ...

# Release lock, child process going to be finished
lock.release()

2014-03-05

Загрузка файла на Amazon EC2 с помощью Python

Есть у меня задачка, с определенной периодичностью заливать через FTP на имеющийся хостинг Amazon EC2 несколько файлов. Некоторое время я делал это тупо через FTP-клиент. Открыл, соединился, нашел нужную папку, закинул файл, закрыл клиент... Вобщем, как обычно.
Но настоящий программист - ленивый программист. Он всегда ищет, как бы автоматизировать рутинные процессы. В решении этой задачи я использую Python.
В составе поставки есть модуль для работы с FTP: ftplib. Однако в нем не нашлось методов для работы с SFTP, использующим для авторизации файл ключа *.pem. Поэтому я использовал pysftp
Использование выглядит простым, как штанга. Пример все пояснит.

srv = pysftp.Connection(host='sitename.org', username='username', private_key='/path/to/file.pem')
srv.execute('mkdir /server/path')
srv.put('/path/to/local/file.html', '/server/path/file.html')
srv.close()

А теперь начнем делать чудеса, доступные пользователям Mac. Создаем каталог, на который навешиваем действие, созданное в Automator. Событие добавления нового файла будет вызывать наш скрипт, передавая ему путь к файлу. После загрузки будет выведено сообщение во всплывающем окне.

2014-01-09

Python и чтение списков

Не так давно меня спросили - как бы ты прочитал список в Python в обратном порядке? Я предположил, что самым естественным образом будет просто выборка из списка по индексу от максимального (равного len(x) - 1) в порядке уменьшения. Сегодня я решил проверить варианты с подсчетом производительности. Python 2.7.4 x64, 8 Гб RAM, AMD Phenom II X4 960T, 3.00 GHz.

Итак, создаем список из ста миллионов записей и проходим по нему разными способами.


big_list = range(100000000)
big_list_len = len(big_list)

# Прямой перебор, самый быстрый и естественный: 10.3 sec
for item in big_list:
    a = item

# Перебор с выборкой по индексу в прямом порядке: 12.8 sec
for num in xrange(big_list_len):
    a = big_list[num]

# А теперь в обратном порядке: 12.7 sec
for num in xrange(big_list_len-1, 0, -1):
    a = big_list[num]

# И наконец, используем встроенную функцию reversed: 11.9 sec
for item in reversed(big_list):
    a = item

Выводы очевидны. Самый быстрый способ взять данные в обратном порядке - использовать reversed и прямой перебор. Выборка по индексу - не намного, но медленнее.

2013-07-29

Неожиданное применение автоматического тестирования

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

Когда форма была исправлена, встал вопрос: что делать? Вопрос "Кто виноват", понятное дело, не стоял. Исправление можно было произвести двумя путями: один - это писать скрипт, учитывающий все условия формы и прогонять сохранённые данные через него. Можно, но времени разбираться на этот момент не было (форма всё-таки и правда не тривиальная была). Второй способ - это заставить пользователей снова открывать форму для редактирования и пересохранить данные, т.е. просто открыть страницу с определённым URL и нажать кнопку "Сохранить". Конечно, они в этом не виноваты, и страдать не должны. Делать это мне? Ещё дольше, чем скрипт писать!

А почему бы не поручить это кому-нибудь, у кого много свободного времени, кто не будет возражать и способен делать рутинную работу без всякого ропота? Среди людей таких нет. Тогда обратимся к роботам.

Ну и тут, как вы догадались, Selenium спешит на помощь. Буквально несколько минут, и готов тестовый скрипт, который займётся всей работой. Запускаем Selenium server ...


java -jar selenium-server-standalone-2.33.0.jar

... и наш скрипт ...


python test.py

... и наблюдаем, как человек-невидимка открывает браузер, переходит по ссылкам и нажимает кнопки. Что нам и нужно было.

А вот, собственно, скрипт.


#!/usr/bin/env python
#coding:utf8

from selenium import webdriver
import unittest
from selenium.webdriver.common.desired_capabilities import DesiredCapabilities

class Wd(unittest.TestCase):
 def setUp(self):
  self.driver = webdriver.Remote(
   command_executor='http://0.0.0.0:4444/wd/hub',
   desired_capabilities=DesiredCapabilities.FIREFOX)
  self.driver.implicitly_wait(30)
  self.base_url = "http://my.address.ru"

 def test_saveButtonClick(self):
  driver = self.driver
  for id in [222706,426061,441101]:
   url = "%s/edit_page_%d" % (self.base_url,id)
   driver.get(url)
   driver.find_element_by_id("saveform").click()

 def tearDown(self):
  self.driver.quit()

if __name__ == "__main__":
 unittest.main()

2013-04-02

Цветной Python: выводим в консоль красиво

Наигравшись с nodejs возвращаюсь к старому доброму python. Запишу себе на память шпаргалку о том, как сделать стандартный вывод в консоль цветным.
Используются стандартные ANSI escape code, применение выглядит так:

print '\033[36mCYANCOLOR\033[0m'
print '\033[96mINTENSIVECYANCOLOR\033[0m'

Кодов 109 штук, но применимыми выглядят только эти:


  • 0 - по умолчанию
  • 1 - усиленный (жирный или более интенсивный цвет)
  • 2 - ослабленный (нежирный или менее интенсивный цвет)
  • 4 - подчёркнутый
  • 7 - негативные цвета
  • 8 - скрытый
  • 9 - перечёркнутый
  • 30 - черный
  • 31 - красный
  • 32 - зелёный
  • 33 - желтый
  • 34 - синий
  • 35 - пурпурный
  • 36 - голубой
  • 37 - белый (светло-серый)
  • 40-47 - аналогично 30-37, но цвет применяется к фону
  • 90-97 - аналогично 30-37, но цвет более интенсивный
  • 100-107 - аналогично 40-47, но цвет более интенсивный


2011-03-28

Использование Python для управления документами OpenOffice.org

# Установка библиотеки uno для python
sudo apt-get install python-uno

Запуск OpenOffice с возможностью им управлять, т.е. в "слушающем" режиме. Несколько опций:
-writer : чтобы сразу запустился Writer
-headless : если хотим запустить без GUI

Остальные по вкусу: -nologo -nofirststartwizard -norestore, понятно по названию.

soffice "-accept=socket,host=localhost,port=2002;urp;" -writer -headless

Если OOo запущен в режиме без GUI, выключить его можно так:
killall soffice.bin

Начинаем наш код.

import uno
local = uno.getComponentContext()
resolver = local.ServiceManager.createInstanceWithContext("com.sun.star.bridge.UnoUrlResolver", local)
context = resolver.resolve("uno:socket,host=localhost,port=2002;urp;StarOffice.ComponentContext")

# Загрузка сервисов
desktop = context.ServiceManager.createInstanceWithContext("com.sun.star.frame.Desktop", context)

# Получить текущий документ
document = desktop.getCurrentComponent()

# Создать новый документ
document = desktop.loadComponentFromURL("private:factory/swriter", "_blank", 0, ())

# Загрузить существующий документ
document = desktop.loadComponentFromURL("file:///home/lucas/myfile.odt" ,"_blank", 0, ())

# Установить курсор
cursor = document.Text.createTextCursor()

# Добавим некий текст
document.Text.insertString(cursor, "Этот текст был добавлен в OOo путем использования python и пакета uno.", 0)

# Переносы строк: \n, отступы: \t
document.Text.insertString(cursor, "\n\n\tНовый параграф.", 0)

# Получаем свойство символа - кегль шрифта
cursor.getPropertyValue("CharHeight")

# Изменяем свойства символов: кегль и имя шрифта, его жирность
cursor.setPropertyValue("CharHeight", 20)
cursor.setPropertyValue("CharFontName", "Arial")
cursor.setPropertyValue("CharWeight", 150)

# Создаем таблицу шириной 2 колонки и высотой в 6 строк
mytable = document.createInstance("com.sun.star.text.TextTable")
mytable.initialize(6,2)
# Вставляем таблицу в страницу
document.Text.insertTextContent(cursor, mytable, 0)

# Заполняем несколько полей
mytable.getCellByName("A1").setString("Полюшко-поле A1")
mytable.getCellByName("B2").setString("Поле, русское поле B2")
mytable.getCellByPosition(1,4).setString('А можно и так')

# Сохраняем
document.store()

# Сохраняем как новый(другой) файл
document.storeAsURL("file:///home/lucas/myfile2.odt",())

# Сохраняем как копию, и остаемся в том-же документе
document.storeToURL("file:///home/lucas/myfile2.odt",())

# Завершаем работу с документом
document.dispose()

# Поиск и замена. Работаем со строками
import string

# Создаем дескриптор поиска
search = document.createSearchDescriptor()

# Что ищем?
search.SearchString = u"Люк"

# Ищем первое найденное
found = document.findFirst( search )

# заменяем найденное и ищем дальше, пока не закончатся искомые фразы
while found:
found.String = string.replace( found.String, u"Люк", u"Люк, я твой отец" )
found = document.findNext( found.End, search)

# Некоторые возможности поиска: чувствительность к регистру или поиск слов
search.SearchCaseSensitive = True
search.SearchWords = True

# Или поиск по регулярным выражениям
search.SearchRegularExpression = True
search.SearchString = "\\<(k|s|v|z|o|u|i|a) "

# Экспорт в PDF. Придется импортировать соответствующую библиотеку
from com.sun.star.beans import PropertyValue
property = (
    PropertyValue( "FilterName" , 0, "writer_pdf_Export" , 0 ),
)
document.storeToURL("file:///home/lucas/myfile2.pdf",property)

# Импорт разрывов шаблона страницы, вставка разрыва в конец документа и импорт еще одного документа из файла
from com.sun.star.style.BreakType import PAGE_BEFORE, PAGE_AFTER
cursor.gotoEnd(False)
cursor.BreakType = PAGE_BEFORE
cursor.insertDocumentFromURL("file:///home/lucas/temp.odt", ())


Оригинал статьи на английском (немного добавил от себя и кое-что изменил)

2010-06-28

Видео для android

Устройства на базе Google Android воспроизводят видео определенного формата (mp4). Чтобы сжать кино для просмотра можно использовать ffmpeg, собранный с кодеком x264.

К примеру, такой скрипт в арсенале андроидовода может прижиться. Назовем его android.sh, сделаем исполняемым и положим в какую-нибудь папку, проходящую по данным PATH.



Теперь сжатие файла выполняется командой:
android.sh FileName
или, если нужно выбрать звуковую дорожку
android.sh FileName 1

Пока что мне не попадалось видео, которые бы конвертировались криво. Ура ffmpeg-у.

А как быть с субтитрами?

Единственный плейер, который умеет играть видео с субтитрами (из тех, что мне известны) - mVideoPlayer (даже умеет их сдвигать на указанное кол-во миллисекунд в обе стороны). Единственный тип субтитров, которые он кушает - SRT. Соответственно, в кодировке UTF-8.
Значит, нужно конвертировать субтитры из других форматов. Специальные программы есть в репозитариях, но уже на третьем файле линуксоид подумает: а нельзя ли как-то это автоматизировать? Я искал что-нибудь консольное, и не нашел.
Пришлось, как всегда, брать python в руки. Пока что соорудил конвертировщик ASS в SRT, понадобится другой формат переводить - думаю, и с ним проблем не будет. На выходе отдает файл в исходной кодировке. Вот что получилось на скорую руку (создайте файл подобный предыдущему, например subconv.py)



Теперь файл конвертирую так:
subconv.py -f FileSub.ass
и создается FileSub.srt в той-же папке.
Для конвертирования пачки использую цикл:
for i in *.ass; do subconv.py -f "$i"; done

P.S. Буквально вчера было обновление mVideoPlayer, в котором добавилась поддержка субтитров SSA и ASS. Теперь стало еще легче.

2010-04-19

Создаем DEB-пакет

Сегодня мы будем учиться готовить стандартные Debian-ские пакеты.
Оказывается, это не так сложно, и к тому же интересно.

Немного информации о пакете

Если вкратце, то deb-пакет - это ar-архив, распаковать который можно так:
$ ar x mydebpackage.deb

В результате мы увидим извлеченные файлы:
data.tar.gz (tar-архив с файловой структурой, копируемой в систему при установке)
debian-bynary (файл с номером версии формата пакетов)
control.tar.gz (tar-архив управляющей информации о пакете)

В файле control.tar.gz содержатся интересные вещи:
- postinst (скрипт, выполняемый после установки)
- postrm (скрипт, выполняемый после удаления)
- md5sums (контрольные суммы файлов)
- control (остальная метаинформация)

Например, в control может быть примерно такое содержимое:

Package: mypackage (название пакета)
Version: 0.1 (версия)
Depends: liba, libb1, libc2 (зависимости)
Recommends: libpamparam, lalala (рекомендовано)
Suggests: libbestdeb (могут улучшить работу пакета)
Section: utils
Priority: optional
Description: My best package! But for what?...



Создаем свой DEB

Чтобы создать deb-пакет, у нас есть все, что нужно. К примеру, я хочу собрать пакет видео-утилит, назовем его videotools.
В него войдут bash-скрипты для извлечения из видеофайлов отдельно аудио-дорожки, видео-дорожки, конвертирования avi в flv и обратно.

Для начала, создаем структуру каталогов.

videotools (каталог, в котором все остальное)
- DEBIAN/control (каталог DEBIAN с файлом control)
- usr/local/bin/getaudio (этот и остальные - мои скрипты)
- usr/local/bin/getvideo
- usr/local/bin/flv2avi
- usr/local/bin/avi2flv

Содержимое файла control будет примерно таким:

Package: videotools
Version: 0.1
Maintainer: Kawaikunee <secret@mail>
Description: Contaned scripts getaudio, getvideo, flv2avi, avi2flv
Section: utils
Priority: optional
Architecture: all
Depends: bash, mplayer, mencoder

Архитектуру указываю любую, это же просто bash-скрипты.

Теперь собираем пакет:
$ dpkg-deb --build videotools
И получаем файл videotools.deb

Трубим в фанфары и кушаем мороженое в честь успеха.

Дополнительная информация

Виртуальный пакет - это общее имя, употребляемое к любому из пакетов некой группы, все пакеты из которой обеспечивают подобную базовую функциональность (например, программы для работы с электронной почтой).
Чтобы добавить файл в виртуальный пакет какой-либо функциональности, нужно указать это в файле control, например так:
Provides: mail-transport-agent
Чтобы сделать файл зависимым от чего-то общего, так:
Depends: mail-transport-agent

Метапакет - пакет, в котором нет реальных файлов, он содержит только зависимости, чтобы гарантированно установить их. Например - ubuntu-standard

Создание DEB-пакета из исходников

Никакой магии. Устанавливаем пакет checkinstall (я уверен, не нужно вас учить, как это делается ^_^). Как обычно, в папке с распакованными исходниками делаем
$ ./configure && make
а вместо
$ sudo make install
набираем
$ sudo checkinstall
Такой финт ушами сначала создаст deb-пакет, а потом установит его через apt, что в дальнейшем обеспечит его корректное удаление, даже если разработчик не предусмотрел такой возможности. К тому-же, у нас останется пакет, который мы можем переслать друзьям, менее сведующим в этих консольных тонкостях, или которым это вовсе ни к чему.

Создание DEB-пакета для программ на Python

Для этого используем пакет py2deb(http://www.manatlan.com/page/py2deb). Приведены очень доступные примеры, а сам пакет скачивается там же, на сайте разработчика.
Например, вот скрипт, которым я упаковываю программу для изучения Библии. Оцените простоту!

#!/usr/bin/env python
#-*- coding:utf-8 -*-

from glob import glob
from py2deb import Py2deb

p=Py2deb("bible")

p.author="Kawaikunee"
p.mail="secret@mail"
p.description="Программа в помощь изучающим Библию. Просмотр, поиск, параллельные места. Выделить текст и нажать клавишу F: поиск по выделенному. Клик на номере стиха: показать параллельные места. Клик на названии отрывка в найденном: открыть эту главу."
p.url = "http://kawaikunee.blogspot.com"
p.depends="python-gtk2, python, python-pysqlite2, msttcorefonts"
p.license="gpl"
p.section="utils"
p.arch="all"

p["/usr/bin"] = ["bible.py|bible","bible_iface.py","bible_shift.py"]
p["/usr/share/Bible/src"] = ["ot.png","nt.png","ap.png","ava.svg","bible_shift.xml"]
p["/usr/share/Bible/modules"] = ["modules/rst.sqlite|rst.sqlite","modules/kjv.sqlite|kjv.sqlite","modules/bible.db|bible.db"]
p["/usr/share/Bible"] = glob("modules/para/*")
p["/usr/share/applications"]=["Bible.desktop|Bible.desktop"]

p.generate("0.3.1")

Воодушевил? Дерзайте!

2010-04-05

Работа с MSSQL Server в консоли linux

По работе приходится использовать MSSQL Server, никуда не денешься. И это одна из причин, почему у меня на работе два компьютера - один под MS Windows XP (где и стоит SQL Server), второй - Linux Mint 8 Helena.
Что делать, когда раз-два в день (или в неделю) необходимо выполнить пару запросов к этому серверу баз данных? Ну, для начала нужно пересесть за компьютер с изделием MS (или воспользоваться удаленным доступом), запустить SQL Server Managment Studio Express, подождать, пока этот монстр загрузится, залогинится, открыть окошко для запроса, и только после этого можно начать работу.
Как много букв! Как хотелось бы чего-нибудь попроще и побыстрее! Что-то типа консоли mysql. Что-ж, достаточно приложить руки и голову, а python сделает остальное.
Вот, что у меня получилось. Мне нравится.

2010-03-02

Сайт церкви стартовал

1 марта 2010 года запущен сайт церкви, реконструированный на Pylons(web-фреймворк на Python). В дальнейшем будут добавляться новые возможности, пока это базовая часть. Ознакомиться можно здесь.

2010-02-17

Некоторые различия при установке Python Imaging Library (PIL)

Установка через APT
$ sudo apt-get install python-imaging
$ python
>>>from PIL import Image

Установка через easy_install:
$ easy_install PIL
$ python
>>>import Image

На заметку.