Python vs Perl: Python против Perl


Читатель спросит: «Зачем сравнивать два таких несравнимых языка?» Отвечу. Perl и Python, будучи очень разными языками, всё же имеют много общего. Они универсальны (в отличии от, скажем, PHP), очень развиты (в отличии от shell, awk и подобных) и они оба являются скриптовыми языками (в отличии от не менее мощных C/C++, Pascal и других). Таким образом, они часто соперничают на одной территории и сравнивать их приходится.

У каждого заголовка я буду в скобках подписывать язык, который в данном аспекте имеет преимущество.

Объектно-ориентированное программирование

Классы (Python)
Python — изначально объектно-ориентированный язык, в Perl классы — достаточно искусственное образование.

Вот объявление двух классов (базовый и производный), конструирование объекта и вызов метода:

Perl

package A;
sub new {
my ($class, $s)=@_;
return bless {'string' => $s}, $class;
}
package B;
our @ISA=qw/A/;
sub type {
my ($self)=@_;
print $self->{'string'}, "\n";
}
package main;
my $obj=B->new('ok');
$obj->type();

Python

class A:
def __init__(self, s):
self.string=s
class B(A):
def type(self):
print self.string
obj=B('ok')
obj.type()

Как видно, синтаксис Python намного естественней и компактней во всём: и в объявлении классов, и при организации наследования, и при доступе к данным и методам объекта.

Причём в Perl разные классы традиционно разносят в разные файлы, что затрудняет чтение программы, да и производительности не способствует.

Перегрузка операторов (Python)

Перегрузка операторов в Perl возможна, но является очень ограниченной и искусственной надстройкой. В Python это одно из базовых свойств языка. Больше того, в Python можно переопределить и поведение объекта так, чтобы он эмитировал базовые типы (в Perl для этого есть tie-механизм, но он намного слабее). Вот пример итератора на Python:

class Pow:
def __init__(self, limit):
self.n=1
self.limit=limit
def __add__(self, n):
return Pow(self.limit + n)
def __iter__(self):
return self
def next(self):
self.n *= 2
if self.n > self.limit:
raise StopIteration
return self.n

a=Pow(300)
for i in a:
print i
b=a+800
for i in b:
print i

Объект класса Pow(N), при переборе в цикле, ведёт себя так, как будто-то это массив степеней двойки, не превосходящих N. Обратите внимание на то, что сам массив при этом не создаётся (что экономит память), его элементы вычисляются «на лету» (если цикл будет прерван, то лишние вычисления выполняться не будут).

Кроме того, объект поддерживает операцию сложения с числом.

Первый цикл этой программы распечатает степени двойки, не превосходящие 300 (2, 4, 8, 16, …, 256), второй цикл — не превосходящие 1000.

Естественно, можно переопределить все операторы. Имеется гибкий (полностью управляемый) и мощный механизм преобразования типов.

Это упрощённый пример, внимательный читатель наверно заметил, что объект допускает лишь однократное использование. Для получения полной функциональности, следует создать класс итератора. В него выносится метод next, а метод __iter__ должен возвращать не self, а объект класса итератор.
Исключения (Python)

В Python имеется полноценный механизм обработки исключений, встроенный в язык.

В Perl исключения можно эмулировать, при помощи таких модулей, как Error.pm и Exception.pm, не входящих в стандартную поставку Perl. Они основаны на умелом использовании eval/die, но это весьма искусственный приём, который никак не может обеспечить той гибкости, которую даёт Python.

В Python исключения являются классами, обработка исключений ведётся с учётом структуры этих классов, что позволяет отслеживать целые группы исключений. Кроме того, на каждый класс исключений можно сделать свой обработчик.
Создание объектов (Python)

В Python конструктор всегда создаёт объект, что весьма логично. Если возникла ошибка, то Perl, традиционно, возвращает undef, Python же генерирует exception.

Пример обработки ошибки при создании объекта:
Perl

my $obj=AnyClass->new() or die 'error!'

Python

try:
obj=AnyClass()
except AnyClassException exc:
print exc
sys.exit(1)

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

Кроме того, если вы не позаботитесь об обработке ошибки в Perl, то программа продолжит «работать», что затрудняет диагностику реальных причин ошибок. В Python, не обработанное вами исключение будет обработано самим интерпретатором (естественно, исполнение программы будет прервано).

Общее удобство языков

Синтаксис
Синтаксис Python максимально компактен. В нём нет ни фигурных скобок, ни точек с запятой, ни оператора -> ни двойных двоеточий :: (всегда используется «.»). У переменных нет префиксов. Скобки можно опускать везде, где только возможно:
Perl

($a, $b)=($b, $a)

Python

a, b = b, a

За это приходится платить двумя неудобствами. Во-первых, в Python важен отступ, он обозначает блок. Однако это правило можно нарушать везде, где только можно, и оно не создаёт неудобств. Пример:

if a > b:
func(1,
2,
3)

Отступ во второй строке важен, остальные не важны.

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

Perl

@a=(1);

Python

a=(1,)

Что, впрочем, тоже не доставляет особых неудобств.

Строго говоря, эти два фрагмента кода не совсем эквиваленты. Python создаёт объект-массив, то есть ссылку на массив. Поэтому Perl-эквивалент правильнее было бы написать как $a=[1]. Кроме того, писать скобки в Python не обязательно. Вполне допустима запись a=1, (запятая входит в выражение).

Пространства имён (Python)
В Perl все переменные глобальны, если не указано иное. Но чаще всего требуется именно иное.

В Python, на против, все переменные локальны (если не указано иное, глобальные переменные так же допустимы).

Функции (Python)
В Python аргументы функций задаются как во всех нормальных языках, а не в магическом массиве, как в Perl.

Perl

sub f {
my ($a)=@_;
return $a*2
}

Python

def f(a):
return a*2

Декораторы (Python)

Python имеет механизм декораторов, которого нет в Perl. Подробнее о декораторах можно почитать тут http://www.ibm.com/developerworks/ru/library/l-cpdecor/index.html.
Условия (?)

Python допускает короткую форму записи сравнений:

Perl

if (1 < $a and $a < 2) { print $a }

Python

if 1 < a < 2: print a

Но в Python нет характерной для Perl записи условий после выражения:

Perl

print 'error' unless $ok

Python

if not ok:
print 'error'

Этой возможности иногда не хватает (как и оператора unless, которого в Python тоже нет).

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

Perl

$url=~s{[^a-zA-Z0-9_.-]}
{sprintf('%%%02X', $&)}ge;

Python

url=re.sub(r'[^a-zA-Z0-9_.-]',
lambda a: '%%%02X' % ord(a.group()[0]),
url)

Это создаёт некоторые неудобства, но не только.

Благодаря такой организации Python оказывается, в среднем, раз в десять быстрее, чем Perl. Именно потому, что при обработке выражений не создаётся дополнительных переменных, как в Perl.

Perl-программист, помни, что если ты используешь переменные типа $& хотя бы в одном месте программы, то все(!) регулярные выражения начинают притормаживать. Простое выражение типа m/a/ может начать работать в десятки раз медленнее (точная цифра зависит от длинны обрабатываемых строк). Больше того, если переменные типа $& используются каким-то модулем, то результат будет столь же печален, а они используется даже в модулях самого общего назначения, на пример diagnostics.
Проверка типов (Python)

Python гораздо более строг, чем Perl:

Perl

a='text'.1
b='2'+2

Python

a='text'+str(1)
b=int('2')+2

Кроме того, Python строго проверяет количество параметров, передаваемых функции (если в определении функции не оговорены значения по умолчанию или функция не поддерживает вызов с переменным числом аргументов).

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

Скороговорка (Python)
В Python и не пахнет скороговоркой Perl, однако здесь имеется собственная скороговорка. Вот пример программы на Perl, которая выводит список всех jpg-фалов в директории:

Perl

while(<*.jpg>) {
print "$_\n"
}

Python

for i in glob.glob('*.jpg'):
print i

В Python это пришлось писать «честно», хотя стандартная библиотека glob очень помогла.

Кроме того, Python не имеет regexp-скороговорки (см. выше) и некоторых полезных операторов: «++», «—», «?:» (последний появился в 2.5). Диапазоны, записываемые в Perl с помощь двух точек «..», в Python генерируется специальной функцией.

Python не поддерживает магических переменных типа $_ (вернее в Python есть переменная _, но она доступна только в интерактивном режиме, когда вы вводите команды с консоли).

Для Perl-программиста всё это не привычно, но на самом деле, отсутствие этих «удобств» позволяет увеличить производительность.

Однако в Python есть свои компактные конструкции. Например проверить вхождение элемента в список в Python проще:

Perl

if (grep {$_ eq 'z'} @a) {
print "find!\n";
}

Python

if 'z' in a:
print 'find!'

В Python можно сравнивать любые объекты. Вот Python-код, которому невозможно найти компактный аналог в Perl:

if (1, 2, 3) in ((1, 2, 3),
(2, 3, 1),
(3, 1, 2)):
print 'массив массивов содержит массив (1, 2, 3)'

К Python-скороговорке я бы отнёс и то, что в Python нет двухсимвольных конструкций «->» и «::»; их роль выполняет точка. Здесь же следует упомянуть цепочки сравнений:

Perl

if ($a < $b and $b < $c) {

Python

if a < b < c:

Отсутствие в Python префиксов переменных («$», «@», «%» и других) так же делает код более компактным.

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

Возможности Python удачно используются во многих встроенных типах. На пример дескриптор файла является (кроме прочего) итератором. Поэтому построчное чтение можно выполнить очень просто:

fh = open('file', 'r')
for line in fh:
print line,
fh.close()

При этом, естественно, используются все преимущества итераторов, что позволяет не читать файл целиком, а читать его именно построчно.

Циклы
К сожалению, Python не поддерживает классический for в стиле C. Следующий цикл, выдающий буквы от 'A' до 'F', на Python реализуется менее изящно:

Perl

for (my $i='A'; $i lt 'G'; $i++) {
print $i
}

Python

for i in range(ord('A'), ord('G')):
print chr(i)

Справедливости ради, следует сказать, что написав один раз класс-помощник (на подобии того, что был показан выше), можно «научить» Python делать подобный перебор более изящно и производительно, чем Perl. Но циклы всё равно не помешали бы.

Кроме того, Python-for умеет выполнять функции, аналогичные Perl-map:

Perl

@a = map {$_*2} (1, 2, 3)

Python

a = [x*2 for x in (1, 2, 3)]

Эту Python-конструкцию можно сочетать с условиями:

Perl

@a = map {$_*2} grep {$_ % 2} (1, 2, 3)

Python

a = [x*2 for x in (1, 2, 3) if x%2]

Набор функций для отладки

Perl поддерживает целый ряд функций, полезных на этапе отладки: warn, die, caller. В Python не сложно создать их аналоги (даже более мощные), но встроенных аналогов в Python нет.

Однако, в Python имеется гибкий механизм трассировки, которого нет в Perl. Кроме того, диагностические сообщения, выдаваемые Python в случае возникновения ошибки гораздо более информативны, чем аналогичные сообщения Perl.

Я бы сказал, что и Python и Perl имеют очень много инструментов для отладки, но сами подходы к отладке очень сильно отличаются. Python более ортодоксален и фундаментален, Perl позволяет работать «на скорую руку», что тоже бывает удобно.

Интроспекция (Python)
В Python есть прекрасные средства интроспекции. То есть во время исполнения программы вы всегда можете узнать, какие методы поддерживает тот или иной объект, каковы их интерфейсы и даже получить справку.

Здесь возможности Perl и класса UNIVERSAL на порядок слабее.

Библиотеки
Оба языка имеют обширные библиотеки. Я не сталкивался с тем, что какая-то функция реализована в библиотеках только одного языка.

Следует особо отметить, что Python хранит библиотеки в откомпилированном виде (код на языке Python преобразуется во внутреннее представление), что многократно ускоряет их загрузку.

Документация
Документация, на мой взгляд, лучше всего иллюстрирует общее настроение разработчиков.

Документация Perl — это сборник рассказов, со множеством примеров, пояснений, предупреждений и просто остроумных выражений (а уж сколько их в исходном коде!).

Документация Python не содержит ничего лишнего, это классическая техническая документация. Читать наискосок её нельзя, но внимательно чтение даёт исчерпывающую информацию.

Под капотом
Тем, кто ценит не только удобство программирования, но производительность будет интересно заглянуть под капот Perl и Python.
Python быстрее за счёт настоящей локальности переменных

Локализация переменных в Perl и в Python происходит по разному. Python создаёт локальные пространства имён и просматривает их в первую очередь. Таким образом, пространства имён получаются намного компактней и поиск по ним идёт быстрее.

На заметку знатокам Python: имена функций принадлежат к глобальному пространству имён и поэтому функции вызывают такое же замедление, какое свойственно Perl. Если вам необходимо вызывать функцию много раз, то создание её локальной копии позволит значительно ускорить работу программы.

Здесь функция p работает «медленно»:

def f(): pass
def p():
for i in xrange(0, 100000):
f(); f(); f(); f(); f(); f();
… много раз …
f(); f(); f(); f(); f(); f();

Здесь — намного быстрее:

def q(): pass
def p():
f=q
for i in xrange(0, 100000):
f(); f(); f(); f(); f(); f();
… много раз …
f(); f(); f(); f(); f(); f();

Ускорение произошло потому, что теперь имя f принадлежит локальному пространству имён и доступ к нему происходит быстрее.

Python быстрее за счёт copy-on-write
Python не создаёт копию данных никогда, если в том нет необходимости.

Шаг первый: присвоим некой переменной некое значение:

a=1
print id(a) # 136205900

Шаг второй: выполним присвоение:

b=a
print id(a) # 136205900
print id(b) # 136205900
print a is b # True

Как видите, пока, a и b — это одни и тот же объект, занимающий одну и туже память.

Аналогичные действия в Perl приведут к копированию:

$a=1;
$b=$a;
print \$a; # SCALAR(0x804db08)
print \$b; # SCALAR(0x804db2c)

Шаг третий: присвоим b новое значение:

b=2
print id(a) # 136205900
print id(b) # 135971708
print a is b # False

Как видите, только теперь a и b стали разными объектами.

Сборка мусора
То, что в Perl называется «механизмом сборки мусора», в Python называется гораздо скромнее — «механизм подсчёта ссылок». Эти механизмы работают одинаково и не способны, на пример, удалять циклические ссылки. Вот пример программы на Perl, вызывающей утечку памяти, так как здесь подсчёт ссылок бессилен:

while(1) {
my $x;
$x=\$x;
}

Создать подобную утечку памяти в Python практически невозможно, так как Python имеет второй механизм сборки мусора (он-то и называется в Python «механизмом сборки мусора»). Он способен удалить циклические ссылки или вернуть программисту указатели на эти потерянные структуры с целью их корректного удаления. Этот механизм можно включать, выключить и запускать принудительно, когда это необходимо.

Компиляция (грамматика)
Python использует самую простую грамматику (LL(1)), позволяющую компилировать программу в один проход.

Perl не имеет никакой осмысленной грамматики (см. perlfaq7), это приводит не только к массе внутренних противоречий и неочевидностей в языке, но и катастрофически замедляет компиляцию программы.

Речь идёт о Perl 5.8 и более ранних. В версии 5.10, которая пока не вышла, обещают реализовать BNF-грамматику (чуть подробнее см. «Python vs Perl II»). Но даже если это произойдёт, простота LL(1) грамматики не будет достигнута.

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

@a = map { {$_ => $_} } qw/a b/;

@a = map { $x=$_; {$x => $_} } qw/a b/;

А вот ещё пример. Вот этот код работает:

my $fh = $self->{'file'};
my $m = <$fh>;

А более компактная запись уже не работает:

my $m = <$self->{'file'}>; # не работает

В данном случае, Perl не в состоянии разобрать собственную синтаксическую конструкцию, написанную абсолютно корректно. Не спасают ни пробелы, ни скобки.

Отсутствие в Perl продуманной грамматики заметно даже невооружённым взглядом. Даже не заглядывая под капот, можно заметить, на пример, следующее.

В Perl существует конструкция if/elsif/else; на ряду с if имеется и unless; но для него нет аналога elsif. То есть вы, конечно можете использовать конструкцию unless/elsif/else, но не существует чего-то подобного unless/elsunless/else и/или if/elsunless/else.

Если бы язык подчинялся бы единой логике, то что-то вроде elsunless возникло бы естественным образом, но оно не возникло.

Справедливости ради, следует отметить, что и грамматика Python имеет некоторые «противоречия», о которых я скажу далее.

Компиляция (хранение)
Все модули в Python хранятся в откомпилированном виде (байт код, не зависящий от платформы), что во много раз ускоряет их загрузку.
Но идеален ли Python?

Конечно нет.

Грамматика
Грамматика Python имеет некоторые непоследовательности. На пример, операция сложения эквивалентна вызову метода __add__, но прямой вызов этого метода может дать обескураживающий результат:

2.__add__(2) # это 2+2, но это не работает
# вы получите исключение 'SyntaxError: invalid syntax'

Однако следующие записи работают как положено:

(2).__add__(2) # это 2+2
2.0.__add__(2) # это 2.0+2 (результат не 4, а 4.0)

Вы наверно уже догадались в чём проблема. Как и во многих других языках, символ «точка» в Python выполняет роль десятичного разделителя и обозначает вызов метода. Это не вызвало бы проблем, но запись

2

является, на самом деле, ни чем иным, как вызовом конструктора

int(2)

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

Впрочем, такого рода неоднозначности существуют во многих языках, как, на пример, вы интерпретируете следующий код на С?

a=b/*c

Это деление на указатель, или начало комментария? Здесь тоже необходимы скобки, чтобы снять неоднозначность.

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

Архитектура
Текущая реализация Python имеет неоднозначности в реализации одних и тех же свойств разных встроенных объектов. Я провёл небольшое исследование этого вопроса, результаты которого представил в виде интерактивной таблицы.

Трудно перечислить все архитектурные недоработки.

Стоит отметить, что в Python 3.0 все эти недоработки устранены, правда, ценой потери совместимости.

Сравнения
Как сравнить число со строкой? В Perl этот вопрос решается просто: введены несколько операций сравнения — для чисел и для строк. В Python такого разнообразия нет (и сделать его не представляется возможным, так как в Python существует намного больше встроенных типов, чем в Perl).

Часть разработчиков Python считает, что сравнивать значения разных типов нельзя (я тоже склоняюсь к этому мнению). Но не меньшая часть придерживается противоположной точки зрения. Сам же Python балансирует на грани. И грань эта весьма странная.

На пример сравнение

1-2j < 'michurin'

даёт результат True (почему? — не известно); но сравнение

1-2j < 2-1j

вызывает исключение

TypeError: no ordering relation is defined for complex numbers

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

Эта непоследовательность никогда не создаст трудностей программисту, пишущему качественный код, но всё же она не является украшением Python'a.

Замечу, что в Python 3.0a1 выражение 1-2j < 'michurin' уже не работает; что, на мой взгляд, представляется правильным.


Комментарии запрещены.




Статистика