Введение в PyPy

В новой реализации наука сочетается с практичностью

Как повысить производительность программ на языке Python и сделать их более гибкими с помощью среды PyPy и реализации компилятора just-in-time? Это статья о среде PyPy, ее преимуществах и о том, как она помогает ускорить разработку высокопроизводительных приложений.

Учи Огбаджи, ведущий консультант, Fourthought Inc

Учи Огбаджи (Uche Ogbuji) – консультант и соучредитель Fourthought Inc.. компании, специализирующейся на продаже программного обеспечения и консалтинге в области XML-решений для менеджмента интелектуальный ресурсов предприятий. Fourthought разрабатывает 4Suite, платформу с открытым кодом для XML, RDF и приложений для управления интеллектуальными ресурсами. Мистер Огбаджи является также ведущим разработчиком языка запросов Versa RDF. Он – компьютерный инженер и писатель, родившийся в Нигерии; живет и работает в Болдере (Boulder), штат Колорадо, США. Вы можете найти больше информации о мистере Огбаджи, посетив его блог Copia.



03.12.2012

Обзор

С начала нового тысячелетия язык программирования Python, появившийся в 1994 году, получил широкое распространение. Одним из показателей успеха языка является количество его реализаций. Наиболее известная и популярная реализация Python называется CPython. Были и другие успешные проекты, такие как Jython (язык Python, работающий поверх среды выполнения Java™) и IronPython (язык Python, работающий на платформе .NET). Все это open source-проекты, и Python всегда был широко представлен в мире ПО открытым исходным кодом.

Давняя цель реализации Python ― поддержка чистого дизайна языка для совершенствования определения Python путем описания языка в его собственных терминах, а не в терминах других языков, таких как C и Java. Проект PyPy представляет собой реализацию Python с учетом этого требования. PyPy означает «Python, реализованный на языке Python», хотя на самом деле он реализован на подмножестве языка Python, называемом RPython. Точнее, PyPy представляет собой отдельную среду исполнения, к который можно подключить любой язык.

Чистый дизайн языка PyPy позволяет создавать очень эффективные низкоуровневые оптимизаторы. В частности, в PyPy входит just-in-time (JIT)-компилятор. Это та же технология, которая произвела революцию производительности Java-программ в форме виртуальной машины HotSpot, приобретенной компанией Sun Microsystems у Animorphic в начале 2000-х и включенной в реализацию Java от Sun, что сделало язык подходящим для большинства задач. Python уже применяется для многих целей, но наибольшее количество жалоб вызывает производительность. Оптимизирующий JIT-компилятор PyPy уже демонстрирует, как можно радикально повысить производительность Python-программ и хотя этот проект еще находится в стадии, которую я охарактеризовал бы как позднее бета-тестирование, он уже стал важным инструментом Python-программиста и весьма полезным дополнением к инструментарию любого разработчика.

В этой статье я представляю PyPy, не предполагая наличия у читателя обширного опыта в области работы с Python.

Начало работы

Во-первых, не надо путать PyPy с PyPI. Это очень разные проекты. Последний ― это Python Package Index, сайт и система для сбора дополнений к стандартной библиотеке Python. Оказавшись на нужном сайте PyPy (см. раздел ресурсы), вы обнаружите, что разработчики много сделали для того, чтобы облегчить жизнь большинству пользователей. Если у вас установлена ОС Linux®, Mac или Windows® (за исключением Windows 64, которая пока не поддерживается) на современном оборудовании, то вы сможете легкозагрузить и запустить один из двоичных пакетов.

Текущая версия PyPy 1.8 полностью реализует Python 2.7.2, что означает, что она должна быть совместимой с этой версией CPython по языковым функциям и поведению. Однако во многих показательных приложениях она уже доказала, что она гораздо быстрее, чем CPython 2.7.2, что и вызвало наш к ней интерес. Следующий протокол сеанса показывает, как я установил PyPy на свою машину Ubuntu 11.04. Он взят из более ранней версии PyPy, но PyPy 1.8 дает аналогичные результаты.

$ cd Downloads/
$ wget https://bitbucket.org/pypy/pypy/downloads/pypy-1.6-linux.tar.bz2
$ cd ../.local
$ tar jxvf ~/Downloads/pypy-1.6-linux.tar.bz2
$ ln -s ~/.local/pypy-1.6/bin/pypy ~/.local/bin/

Теперь нужно добавить в $PATH путь ~/.local/bin/. После установки PyPy я рекомендую также установить Distribute и Pip, чтобы облегчить установку дополнительных пакетов. (Я не рассказываю в этой статье о Virtualenv, который представляет собой способ сохранения отдельной, чистой среды Python, но вы можете использовать и его.) Следующий сеанс демонстрирует настройку Distribute и Pip.

$ wget http://python-distribute.org/distribute_setup.py
$ wget https://raw.github.com/pypa/pip/master/contrib/get-pip.py
$ pypy distribute_setup.py
$ pypy get-pip.py

Файлы библиотек будут размещены в каталоге ~/.local/pypy-1.8/site-packages/, а исполняемые файлы ― в каталоге ~/.local/pypy-1.8/bin, который тоже надо добавить в $PATH. Кроме того, убедитесь, что вы используете тот pip, который только что установлен, а не общесистемный. Теперь можно устанавливать сторонние пакеты, которые будут использоваться в этой статье.

$ pip install html5lib
$ pip install pyparsing

В листинге 1 показан результат работы интерпретатора PyPy после вызова "пасхального яйца" Python import this.

Листинг 1. Пример выходных данных PyPy
uche@malatesta:~$ pypy
Python 2.7.1 (d8ac7d23d3ec, Aug 17 2011, 11:51:18)
[PyPy 1.6.0 with GCC 4.4.3] on linux2
Type "help", "copyright", "credits" or "license" for more information.
And now for something completely different: ``__xxx__ and __rxxx__ vs operation
slots: particle quantum superposition kind of fun''
>>>> import this
The Zen of Python, by Tim Peters

Beautiful is better than ugly.
Explicit is better than implicit.
Simple is better than complex.
Complex is better than complicated.
Flat is better than nested.
Sparse is better than dense.
Readability counts.
Special cases aren't special enough to break the rules.
Although practicality beats purity.
Errors should never pass silently.
Unless explicitly silenced.
In the face of ambiguity, refuse the temptation to guess.
There should be one-- and preferably only one --obvious way to do it.
Although that way may not be obvious at first unless you're Dutch.
Now is better than never.
Although never is often better than *right* now.
If the implementation is hard to explain, it's a bad idea.
If the implementation is easy to explain, it may be a good idea.
Namespaces are one honking great idea -- let's do more of those!
>>>>

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

Для анализа я выбрал html5lib, библиотеку на чистом Python, предназначенную для реализации алгоритмов синтаксического анализа рабочей группы WHAT-WG, которая занимается определением спецификации HTML5. HTML5 должен быть обратно совместимым даже с самыми "кривыми" Web-страницами. Таким образом, html5lib вдвойне хорош как инструментарий синтаксического разбора HTML общего назначения. Он протестирован на CPython и PyPy и на последнем работает значительно быстрее.

Листинг 2 анализирует указанную Web-страницу и построчно выводит ссылки из нее. Укажите в командной строке целевой URL-адрес страницы, например: pypy listing1.py http://www.ibm.com/developerworks/opensource/.

Листинг 2. Распечатка ссылок, найденных на странице
#!/usr/bin/env pypy

#Import the needed libraries for use
import sys
import urllib2

import html5lib

#Список кортежей для проверки ссылок, каждый в виде пары атрибут/элемент
link_attrs = [
    ('a', 'href'),
    ('link', 'href'),
]

#Эта функция представляет собой генератор, конструкцию Python, 
#которая может использоваться как последовательность.
def list_links(url):
    '''
    Given a URL parse the HTML and yield a sequence of link strings
    as they are found on the page.
    '''
    #Open the URL and get back a stream of the content
    stream = urllib2.urlopen(url)
    #Parse the HTML content according to html5lib conventions
    tree_builder = html5lib.treebuilders.getTreeBuilder('dom')
    parser = html5lib.html5parser.HTMLParser(tree=tree_builder)
    doc = parser.parse(stream)

    #Во внешнем цикле перебираем каждый набор "элемент/атрибут" 
    for elemname, attr in link_attrs:
        #In the inner loop, go over the matches of the current element name
        for elem in doc.getElementsByTagName(elemname):
            #If the corresponding attribute is found, yield it in sequence
            attrvalue = elem.getAttribute(attr)
            if attrvalue:
                yield attrvalue

    return

#Чтение URL для разбора из первого аргумента командной строки
#Примечание. Списки  Python начинаются с индекса 0, но, как и в UNIX, 0-ым
#аргументом командной строки является имя самой программы
input_url = sys.argv[1]

#Настройка генератора путем его вызова с аргументом URL с последующим перебором 
#полученных строк  ссылок и выводом каждой из них на печать.
for link in list_links(input_url):
    print link

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

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


Более сложный анализ страницы

Большинство задач синтаксического анализа Web-страниц сложнее, чем просто поиск и отображение ссылок, и существует несколько библиотек, которые помогают в решении типичных задач «прочесывания» Web. Pyparsing ― это инструментарий синтаксического анализа общего назначения, написанный на чистом Python, который содержит некоторые средства в поддержку HTML-анализа.

На следующем примере я демонстрирую, как "соскрести" набор статей со страницы указателя IBM developerWorks. Скриншот целевой страницы приведен на рисунке 1. Листинг 3 - это пример записи в HTML-коде.

Рисунок 1. Web-страница IBM developerWorks для обработки
Скриншот страницы технической библиотеки developerWorks со списком статей и их рефератами
Листинг 3. Образец HTML-кода записи, подлежащей обработке
<tbody>
 <tr>
  <td>
   <a href="http://www.ibm.com/developerworks/opensource/library/os-wc3jam/index.html">
   <strong>Join the social business revolution</strong></a>
   <div>
    Social media has become social business and everyone from
    business leadership to software developers need to understand
    the tools and techniques that will be required.
    The World Wide Web Consortium (W3C) will be conducting a
    social media event to discuss relevant standards and requirement
    for the near and far future.
   </div>
  </td>
  <td>Articles</td>
  <td class="dw-nowrap">03 Nov 2011</td>
 </tr>
</tbody>

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

Листинг 4. Листинг 4. Извлечение списка статей с Web-страницы
#!/usr/bin/env pypy

#Импортируем необходимые встроенные библиотеки
import sys
import urllib2
from greenlet import greenlet

#Импортируем то, что нужно, из pyparsing
from pyparsing import makeHTMLTags, SkipTo

def collapse_space(s):
    '''
    Strip leading and trailing space from a string, and replace any run of whitespace
    within with a single space
    '''
    #Разбиваем строку по пробелам, а затем вновь соединяем одиночными пробелами.
    #Затем удаляем начальный и конечный пробелы. Все это инструменты из 
    #стандартной библиотеки Python.
    return ' '.join(s.split()).strip()

def handler():
    '''
    Simple coroutine to print the result of a matched portion from the page
    '''
    #Это запускается, когда код впервые выходит на эту функцию-гринлет
    print 'A list of recent IBM developerWorks Open Source Zone articles:'
    #Затем входим в основной цикл
    while True:
        next_tok = green_handler.parent.switch()
        print ' *', collapse_space(data.title), '(', data.date, ')', data.link.href

#Превращаем регулярную функцию в гринлет, обернув ее
green_handler = greenlet(handler)

#Переходим к обработчику гринлета при его первом появлении
green_handler.switch()

#Читаем страницу начала поиска
START_URL = "http://www.ibm.com/developerworks/opensource/library/"
stream = urllib2.urlopen(START_URL)
html = stream.read()
stream.close()

#Устанавливаем некоторые маркеры для начального и конечного тегов HTML
div_start, div_end = makeHTMLTags("div")
tbody_start, tbody_end = makeHTMLTags("tbody")
strong_start, strong_end = makeHTMLTags("strong")
article_tr, tr_end = makeHTMLTags("tr")
td_start, td_end = makeHTMLTags("td")
a_start, a_end = makeHTMLTags("a")

#Соединяем достаточное число маркеров, чтобы сузить область данных, 
#которые нужно получить со страницы
article_row = ( div_start + SkipTo(tbody_start)
            + SkipTo(a_start) + a_start('link')
            + SkipTo(strong_start) + strong_start + SkipTo(strong_end)("title")
            + SkipTo(div_start) + div_start + SkipTo(div_end)("summary") + div_end
            + SkipTo(td_start) + td_start + SkipTo(td_end)("type") + td_end
            + SkipTo(td_start) + td_start + SkipTo(td_end)("date") + td_end
            + SkipTo(tbody_end)
          )

#Применяем к странице синтаксический анализатор. 
#scanString — это генератор совпадающих фрагментов.
for data, startloc, endloc in article_row.scanString(html):
    #Каждое совпадение передается на обработку в гринлет
    green_handler.switch(data)

Я намеренно сделал листинг 4 таким, чтобы представить Stackless Python-функции PyPy. Короче говоря, это устоявшаяся, альтернативная реализация Python для экспериментов с расширенными функциями потока управления. Большинство Stackless-функций не вошли в другие реализации Python из-за ограничений других сред выполнения, которые сняты в PyPy. Один из примеров ― гринлеты. Это очень легкие потоки, которые совместно решают несколько задач, прибегая к явным вызовам для передачи контента от одного гринлета к другому. Гринлеты позволяют делать некоторые тонкие вещи, характерные для генераторов, а также многое другое.

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

Листинг 4 дает представление о функциях типа сопрограмм и гринлетов в целом, но это полезная идея облегчает понимание PyPy, который полон гринлетов и других Stackless-функций. В листинге 4 всякий раз, когда pyparsing соответствует записи, вызывается гринлет для обработки этой записи.

Ниже приведен пример выходных данных для листинга 4.

A list of recent IBM developerWorks Open Source Zone articles:
 * Join the social business revolution ( 03 Nov 2011 )
 http://www.ibm.com/developerworks/opensource/library/os-wc3jam/index.html
 * Spark, an alternative for fast data analytics ( 01 Nov 2011 )
 http://www.ibm.com/developerworks/opensource/library/os-spark/index.html
 * Automate development and management of cloud virtual machines ( 29 Oct 2011 )
 http://www.ibm.com/developerworks/cloud/library/cl-automatecloud/index.html

Что можно и чего нельзя

Я преднамеренно принял подход, реализованный в листинге 4, но начну с предупреждения: всегда опасно пытаться обрабатывать HTML без весьма специализированных инструментов. Хотя и не так, как в XML, где без соответствующего парсера вообще делать нечего, но HTML весьма сложен и замысловат, даже на страницах, которые соответствуют стандарту, каких, однако, меньшинство. Если вам нужен анализатор HTML общего назначения, то лучшим вариантом будет html5lib. Тем не менее, прочесывание Web ― обычно специализированная операция по извлечению информации в некоей конкретной ситуации. Для такого ограниченного использования pyparsing хорош и предоставляет некоторые тонкие возможности.

Причины, по которым я представил гринлеты, которые в листинге 4 не являются строго необходимыми, становятся более очевидными, когда подобный код распространяется на многие сценарии из реальной жизни. В ситуациях, когда синтаксический анализ применяется в сочетании с другими операциями, подход гринлетов позволяет структурировать обработку наподобие конвейеров командной строки UNIX. В тех случаях, когда задача осложняется работой с несколькими исходными страницами, возникает еще одна проблема: операции urllib2 не асинхронны, и вся программа блокируется всякий раз, когда обращается к Web-странице. Решение этой проблемы выходит за рамки настоящей статьи, но использование передового потока управления, приведенного в листинге 4, учит тщательно обдумывать то, как соединить такие сложные приложения с прицелом на производительность.


Заключение

PyPy ― активно поддерживаемый проект и, конечно, движущаяся мишень, но с ним уже многое можно делать, а высокоуровневая совместимость с CPython означает, что, начав экспериментировать, вы, вероятно, получите прочную запасную платформу для своей работы. Из этой статьи вы узнали достаточно, чтобы приступить к работе, и очаровательные Stackless-функции PyP должны вас привлечь. Я думаю, что производительность PyPy приятно удивит вас, но еще важнее то, что этот язык открывает новые подходы для элегантного программирования без ущерба для быстродействия.

Ресурсы

Научиться

Получить продукты и технологии

Комментарии

developerWorks: Войти

Обязательные поля отмечены звездочкой (*).


Нужен IBM ID?
Забыли Ваш IBM ID?


Забыли Ваш пароль?
Изменить пароль

Нажимая Отправить, Вы принимаете Условия использования developerWorks.

 


Профиль создается, когда вы первый раз заходите в developerWorks. Информация в вашем профиле (имя, страна / регион, название компании) отображается для всех пользователей и будет сопровождать любой опубликованный вами контент пока вы специально не укажите скрыть название вашей компании. Вы можете обновить ваш IBM аккаунт в любое время.

Вся введенная информация защищена.

Выберите имя, которое будет отображаться на экране



При первом входе в developerWorks для Вас будет создан профиль и Вам нужно будет выбрать Отображаемое имя. Оно будет выводиться рядом с контентом, опубликованным Вами в developerWorks.

Отображаемое имя должно иметь длину от 3 символов до 31 символа. Ваше Имя в системе должно быть уникальным. В качестве имени по соображениям приватности нельзя использовать контактный e-mail.

Обязательные поля отмечены звездочкой (*).

(Отображаемое имя должно иметь длину от 3 символов до 31 символа.)

Нажимая Отправить, Вы принимаете Условия использования developerWorks.

 


Вся введенная информация защищена.


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=40
Zone=Open source
ArticleID=848294
ArticleTitle=Введение в PyPy
publish-date=12032012