Сам по себе Python, несом­ненно, явля­ется язы­ком прог­рамми­рова­ния. Но мно­гие оши­боч­но счи­тают, что Python — это всег­да та самая шту­ка, которая идет в ком­плек­те с боль­шинс­твом *nix-сис­тем и запус­кает­ся, если наб­рать python в кон­соли. То есть интер­пре­татор — кон­крет­ную реали­зацию — при­нято ассо­цииро­вать со всем язы­ком в целом. При­мер­но как у ребят, которые пишут на Delphi. А как же оно на самом деле?

На самом деле Python — это далеко не один кон­крет­ный интер­пре­татор. Дей­стви­тель­но, чаще все­го мы име­ем дело с так называ­емым CPython, который мож­но наз­вать эта­лон­ной реали­заци­ей (reference implementation) — интер­пре­тато­ром, на который все осталь­ные дол­жны рав­нять­ся. Это озна­чает, что CPython мак­сималь­но пол­но и быс­тро реали­зует те вещи, которые уже про­писа­ны и добав­ляют­ся с течени­ем вре­мени в стан­дарт язы­ка, содер­жащий­ся во вся­кого рода спе­ках и PEP’ах. И имен­но эта реали­зация находит­ся под прис­таль­ным вни­мани­ем «велико­душ­ного пожиз­ненно­го дик­татора» (это реаль­но сущес­тву­ющий тер­мин, не веришь — глянь в Википе­дии) и соз­дателя язы­ка Гви­до ван Рос­сума.

Но все ска­зан­ное вов­се не зна­чит, что нель­зя прос­то так взять и воп­лотить свою собс­твен­ную реали­зацию опи­сан­ных стан­дартов, рав­но как нич­то не меша­ет написать, к при­меру, свой ком­пилятор для C++. Собс­твен­но, доволь­но боль­шое чис­ло раз­работ­чиков имен­но так и сде­лали. О том, что у них получи­лось, я и хочу рас­ска­зать.

 

Чтобы понять Python, надо понять Python

Од­на из самых извес­тных аль­тер­натив­ных реали­заций Python — это PyPy (быв­ший Psyco), который час­то называ­ют питоном, написан­ным на питоне. У всех, кто слы­шит подоб­ное опре­деле­ние, воз­ника­ет законо­мер­ный воп­рос — как то, что написа­но на том же язы­ке, может быть быс­трее, чем сам язык? Но мы выше уже сош­лись на том, что Python — это общее наз­вание груп­пы стан­дартов, а не кон­крет­ной реали­зации. В слу­чае с PyPy мы име­ем дело не с CPython, а с так называ­емым RPython — это даже не сов­сем диалект язы­ка, это, ско­рее, плат­форма или фрей­мворк для написа­ния сво­их собс­твен­ных интер­пре­тато­ров.

info

В стол­кно­вени­ях пок­лонни­ков лагерей Python и Ruby один из аргу­мен­тов питонис­тов — это то, что раз­работ­чики, которых не устра­ива­ла ско­рость работы язы­ка Ruby, написа­ли яко­бы более быс­трую реали­зацию на RPython. Получил­ся впол­не инте­рес­ный про­ект под наз­вани­ем Topaz.

RPython прив­носит нем­ного низ­коуров­невой магии, которая поз­воля­ет при пра­виль­ном под­боре ингре­диен­тов (пря­мые руки обя­затель­ны, глаз три­тона — нет) добавить раз­ные полез­ные плюш­ки в про­изволь­ный интер­пре­тиру­емый язык (необя­затель­но Python) — нап­ример, уско­рение за счет JIT. Если хочет­ся боль­ше под­робнос­тей об этой шту­ке, а так­же если ты сей­час подумал что‑то вро­де «а как же LLVM?» — доб­ро пожало­вать в FAQ.

Об­щая мысль зак­люча­ется в том, что RPython — слиш­ком спе­цифи­чес­кая реали­зация для того, что­бы пря­мо на нем писать боевые прог­раммы. Про­ще и удоб­нее взять PyPy, даром что он пол­ностью сов­местим с CPython 2.7 и 3.3 в пла­не под­держи­ваемых стан­дартов. Прав­да, в силу спе­цифи­ки внут­ренне­го устрой­ства на нем пока что труд­новато зас­тавить работать те биб­лиоте­ки для эта­лон­ной реали­зации, которые исполь­зуют ком­пилиру­емые си‑модули. Но, к при­меру, Django уже впол­не под­держи­вает­ся, при­чем во мно­гих слу­чаях бега­ет быс­трее, чем на CPython.

Еще в качес­тве одной из полез­ных осо­бен­ностей PyPy мож­но наз­вать его модуль­ность и рас­ширя­емость. Нап­ример, в него встро­ена под­дер­жка sandboxing’а — мож­но запус­кать «опас­ный» про­изволь­ный код внут­ри вир­туаль­ного окру­жения, даже с эму­ляци­ей фай­ловой сис­темы и зап­ретом на внеш­ние вызовы вро­де соз­дания сокетов. А еще в пос­леднее вре­мя доволь­но активно раз­вива­ется про­ект PyPy-STM, который поз­воля­ет заменить встро­енную под­дер­жку мно­гопо­точ­ности (ту самую, с GIL и про­чими нелюби­мыми вещами) на реали­зацию, работа­ющую через Software Transactional Memory, — то есть с абсо­лют­но дру­гим механиз­мом раз­рулива­ния кон­курен­тно­го дос­тупа.

info

Это не сов­сем отно­сит­ся к теме статьи, но если тебя заин­тересо­вала информа­ция об RPython — край­не рекомен­дую взгля­нуть еще на один про­ект по «более ско­рос­тно­му запус­ку» Python. Он называ­ется Nuitka и позици­они­рует себя как «ком­пилятор Python в C++». Прав­да, любопыт­но?

 

Нижний уровень

Кро­ме RPython + PyPy, есть и более прос­той спо­соб уско­рить выпол­нение кода на Python — выб­рать один из опти­мизи­рующих ком­пилято­ров, который рас­ширя­ет стан­дарты язы­ка, добав­ляя более стро­гую ста­тичес­кую типиза­цию и про­чие низ­коуров­невые вещи. Как я выше упо­мянул, RPython (даже при­том, что наз­ванное опре­деле­ние к нему тоже отно­сит­ся) явля­ется слиш­ком спе­цифич­ным инс­тру­мен­том для кон­крет­ных задач, но есть про­ект и более обще­го при­мене­ния — cython.

Его под­ход зак­люча­ется в добав­лении фак­тичес­ки нового язы­ка, явля­юще­гося чем‑то про­межу­точ­ным меж­ду C и Python (за осно­ву был взят про­ект Pyrex, который прак­тичес­ки не раз­вива­ется с 2010 года). Обыч­но исходные фай­лы с кодом на этом язы­ке име­ют рас­ширение pyx, а при нат­равле­нии на них ком­пилято­ра прев­раща­ются во впол­не обыч­ные C-модули, которые мож­но тут же импорти­ровать и исполь­зовать.

Код с дек­лараци­ей типов будет выг­лядеть как‑то так:

def primes(int kmax):
cdef int n, k, i
cdef int p[1000]
result = []
if kmax > 1000:
kmax = 1000
k = 0
n = 2
while k < kmax:
i = 0
while i < k and n % p[i] != 0:
i = i + 1
if i == k:
p[k] = n
k = k + 1
result.append(n)
n = n + 1
return result

В прос­тей­шем вари­анте для такого модуля пишет­ся setup.py при­мер­но с таким содер­жани­ем:

from distutils.core import setup
from Cython.Build import cythonize
setup(
ext_modules=cythonize("myscript.pyx"),
)

И да, это фак­тичес­ки все, боль­ше ничего в общем слу­чае делать не надо.
Кста­ти, если кто‑то сей­час нач­нет воз­мущать­ся, что все типы дав­но уже есть в Numpy и не надо при­думы­вать новый язык, — рекомен­дую почитать вот эту ссыл­ку. Суть в том, что раз­работ­чики про­екта активно работа­ют над сов­мес­тным исполь­зовани­ем и того и дру­гого, что поз­воля­ет на некото­рых задачах получить доволь­но серь­езный при­рост про­изво­дитель­нос­ти.

 

Змея в коробке

Так уж выш­ло, что упо­мяну­тый автор и BDFL ори­гиналь­ной реали­зации Python Гви­до ван Рос­сум сей­час работа­ет в Dropbox, где целая коман­да под его началом тру­дит­ся над све­жим высокос­корос­тным интер­пре­тато­ром Python, который на сей раз называ­ется Pyston. Помимо тысяч­ной вер­сии иска­жения наз­вания неж­но нами любимо­го язы­ка, он может пох­вастать­ся тем, что работа­ет на LLVM с исполь­зовани­ем самых сов­ремен­ных течений в реали­зации JIT-ком­пиляции.

По­ка что эта реали­зация находит­ся доволь­но в зачаточ­ном сос­тоянии, но, по слу­хам, впол­не себе исполь­зует­ся для кон­крет­ных внут­ренних про­ектов в Dropbox, а так­же, по уве­рени­ям раз­работ­чиков, дол­жна сде­лать серь­езный шаг впе­ред с точ­ки зре­ния про­изво­дитель­нос­ти (даже по срав­нению с CPython). Но глав­ный инте­рес она может пред­став­лять в том, что это сво­еоб­разный «лабора­тор­ный полигон для испы­таний», на котором раз­работ­чики игра­ют с раз­личны­ми тех­нологи­ями — нап­ример, там в качес­тве под­клю­чаемо­го пла­гина мож­но исполь­зовать аль­тер­нативу GIL под наз­вани­ем GRWL или Global Read-Write Lock, которая яко­бы реша­ет часть ста­рых доб­рых проб­лем сво­его пра­роди­теля. Суть в том, что, в отли­чие от бло­киро­вания все­го и вся (то есть, гру­бо говоря, обыч­ного мьютек­са, как оно устро­ено в GIL), он вво­дит раз­деление зах­ватных опе­раций на чте­ние и запись, что поз­воля­ет‑таки нес­коль­ким потокам получать одновре­мен­ный дос­туп к дан­ным, не пор­тя их. Еще мож­но упо­мянуть так называ­емый OSR — On-Stack Replacement, который явля­ется сво­еоб­разной внут­ренней маги­ей для запус­ка «тяжелых» методов (похожая шту­ка исполь­зует­ся в JavaScript). Такие дела.

 

Виртуальная реальность

Ес­ли ты прод­рался через пре­дыду­щий раз­дел, то навер­няка уже при­шел к мыс­ли о том, что самый дей­ствен­ный спо­соб запилить новый интер­пре­татор Python — это сде­лать прос­лой­ку поверх сущес­тву­ющей вир­туаль­ной машины или сре­ды исполне­ния. При­мер­но так дела и обсто­ят: две самые раз­витые аль­тер­натив­ные реали­зации (за вычетом упо­мяну­того PyPy) — это про­екты для JVM и .NET/Mono.

 

Jython

Jython — не путать с JPython, похожим про­ектом, но раз­вива­ющим­ся доволь­но вяло! — реали­зация Python, поз­воля­ющая в коде на Python вов­сю и с удо­воль­стви­ем поль­зовать­ся поч­ти все­ми при­мити­вами Java и ощу­тить пре­иму­щес­тва JVM по мак­симуму. Выг­лядит это приб­лизитель­но так (при­мер из стан­дар­тной докумен­тации):

from javax.tools import (ForwardingJavaFileManager, ToolProvider, DiagnosticCollector,)
names = ["HelloWorld.java"]
compiler = ToolProvider.getSystemJavaCompiler()
diagnostics = DiagnosticCollector()
manager = compiler.getStandardFileManager(diagnostics, none, none)
units = manager.getJavaFileObjectsFromStrings(names)
comp_task = compiler.getTask(none, manager, diagnostics, none, none, units)
success = comp_task.call()
manager.close()

Кро­ме того, в Jython есть свое решение ста­рой доб­рой проб­лемы с GIL — здесь его тупо нет. То есть при­сутс­тву­ет весь тот же набор при­мити­вов для работы с потока­ми, толь­ко он исполь­зует не нап­рямую потоки опе­раци­онной сис­темы, а их реали­зацию в Java-машине. При­меры работы с такого рода мно­гопо­точ­ностью (одновре­мен­но с исполь­зовани­ем при­мити­вов как из Python, так и из Java) мож­но пос­мотреть здесь.

Нужно больше Java!

Лю­бите­лям поин­тегри­ровать­ся с Java име­ет смысл обра­тить взор на про­екты JPype, Jepp и JPE. В отли­чие от Jython, они не явля­ются в пол­ном смыс­ле сло­ва аль­тер­натив­ными реали­заци­ями, а лишь пре­дос­тавля­ют слой дос­тупа из CPython для манипу­лиро­вания Java-клас­сами и вза­имо­дей­ствия с JVM, ну и наобо­рот.

 

IronPython

IronPython, в свою оче­редь, написан на C# и поз­воля­ет запус­кать Python внут­ри .NET, одновре­мен­но откры­вая дос­туп к куче встро­енных сущ­ностей плат­формы. Код на нем может выг­лядеть, нап­ример, так:

from System.Net import WebRequest
from System.Text import Encoding
from System.IO import StreamReader
PARAMETERS="lang=en&field1=1"
request = WebRequest.Create('http://www.example.com')
request.ContentType = "application/x-www-form-urlencoded"
request.Method = "POST"
bytes = Encoding.ASCII.GetBytes(PARAMETERS)
request.ContentLength = bytes.Length
reqStream = request.GetRequestStream()
reqStream.Write(bytes, 0, bytes.Length)
reqStream.Close()
response = request.GetResponse()
result = StreamReader(response.GetResponseStream()).ReadToEnd()
print result

Выг­лядит вин­дово, но на прак­тике исполь­зует­ся доволь­но широко. Сущес­тву­ет отличный он­лайн-cookbook, в котором мож­но почер­пнуть еще боль­ше тол­ковых при­меров. Так­же не сто­ит забывать, что в при­дачу к .NET идет еще и рас­ширен­ная под­дер­жка Visual Studio, что может быть доволь­но серь­езным фак­тором в гла­зах любите­лей этой наворо­чен­ной IDE от ребят из Ред­монда.

 

Заключение

Как видишь, тер­рари­ум ока­зал­ся доволь­но велик и есть мно­го реали­заций одно­го из самых популяр­ных сов­ремен­ных язы­ков обще­го наз­начения, что еще силь­нее рас­ширя­ет круг реша­емых им задач. Пусть даже под­дер­жка новых стан­дартов появ­ляет­ся в аль­тер­натив­ных интер­пре­тато­рах с запаз­дывани­ем — но зато все син­такси­чес­кие и архи­тек­турные проб­лемы в таком слу­чае уже обка­таны на CPython. Кро­ме того, раз­ве это не кру­то — не толь­ко знать два раз­ных язы­ка, но еще и уметь опе­риро­вать одним в окру­жении дру­гого? Удач­ной охо­ты на змей!

Оставить мнение