Содержание


Тонкости использования языка Python: Часть 1. Версии и совместимость

Comments

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

Это главное достоинство Python, и поэтому разработчикам, даже являющимся экспертами в других языках программирования, например, C++ или Java, стоит включить в свой арсенал и Python, не как основной язык, но как быстрый инструмент для прототипирования и проверки различных предположений.

Возможности Python

За счёт ряда своих притягательных особенностей, Python приобрёл множество сторонников и начал активно использоваться в самых различных областях:

  • системное программирование, написание сценариев, командных файлов, отдельных утилит операционной системы — область, которая традиционно принадлежала таким языкам как bash, perl, awk, ruby;
  • самодостаточные проекты, начиная с отдельных автономных приложений и заканчивая крупнейшими комплексными проектами, полностью ведущимися на Python (например, проект OpenStack, развивающийся при поддержке RedHat);
  • отдельные плагины к крупным проектам (на C/C++), выполняющие задачи конфигурирования и настройки, например VoIP-проекты телефонных коммутаторов Asterisk и FreeSWITCH, использующие для быстрого написания рутинных процедур встроенные интерпретирующие языки Python, JavaScript, Lua;
  • создание для Python специализированных модулей на C/C++, делающих для Python-проектов доступным всё множество целевых библиотек, существующих в C/C++ (это ещё одна сторона простоты связи Python с C-кодом, но уже "в обратную сторону");
  • Web-программирование с использованием Python, особенно в серверной его части (back-end), использующей принципы CGI — для этих целей также были созданы развитые средства, например, Django;
  • создание Python-приложений с графическим интерфейсом (GUI) — за счёт простоты их разработки и сопровождения - при наличии интерфейсов к большинству известных графических библиотек;

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

Ещё одним стратегическим преимуществом Python является то, что программы на Python могут быть, при соблюдении некоторого минимального набора ограничений, независимы от операционной системы. Один и тот же исходный код, без внесения каких-либо изменений, можно с одинаковым результатом исполнять в Windows, Linux, Solaris, FreeBSD, MacOS и в новых мобильных системах Android и iOS. С помощью такой технологии можно строить мульти-платформенные проекты, не требующие портирования.

Основы синтаксиса и приёмы программирования на Python были неоднократно описаны в массе разнообразных публикаций. Поэтому целью этого цикла статей будет не очередное изложение принципов работы с Python, а разбор на практических примерах некоторых тонких вопросов использования. Подобные вопросы не всегда достаточно освещаются в литературе и относятся в большей степени к семантике языка Python. Эти аспекты особенно важны для программистов, выросших на традиционных, строго типизированных языках, таких как C++ или Java, для лучшего восприятия отличительных принципов программирования на Python.

Версия 2 или версия 3?

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

Одновременно существуют две линии версий реализации Python: линия версий 2 и линия версий 3. На момент написания этого текста последними реализациями являются, соответственно, версии 2.7.5 и 3.3.2 Такая параллельность связана с тем, что при переходе от версии 2 к версии 3 произошли существенные изменения синтаксиса языка, нарушившие совместимость "снизу вверх". Но так как на версии 2 уже было написано очень большое число проектов, то был предусмотрен некоторый период для перехода от версии 2 к версии 3. Этот период, однако, затянулся на несколько лет, начиная с 2008 года, и только в 2013 году Python версии 3 был объявлен "официальным Python" для дистрибутива Ubuntu операционной системы Linux. Но в то же время, в других дистрибутивах Fedora и Debian версией по умолчанию всё ещё является Python 2. В большинстве операционных систем обе версии Python могут быть инсталлированы одновременно, а то, какая версия интерпретатора будет использоваться зависит от формы запуска (команды).

При некоторой тщательности, код на Python можно писать так, что он будет одинаково успешно выполняться как в Python 2, так и в Python 3. Все показанные примеры написаны именно так. Но "одинаково успешно" вовсе не означает "с одинаковым результатом": один и тот же программный код, в принципе, может выполнять различные действия в Python 2 и в Python 3, именно из-за отличий в семантике языка, имеющихся между версиями. Это особенно интересно, и на таких тонких отличиях мы будем специально останавливаться.

Примечание. Самой частой причиной невозможности исполнить под Python 3 код, написанный для Python 2, является использование в коде оператора print. В Python 3 этот оператор перешёл в разряд функциональных вызовов print(). Но в синтаксисе Python 2 также есть вызов print(), наряду с оператором print. Все последующие примеры написаны именно с использованием print(), чтобы они без любых изменений могли запускаться в любой версии Python.

Некоторые конструкции и вызовы версии 3 будут вызывать исключения в версии 2, и наоборот. Такие "опасные" конструкции будут выполняться внутри оператора try. Пример такого исполнения показан в листинге 1 (его исходный файл fact2.py находится в архиве python_intro.tgz в разделе "Материалы для скачивания"):

Листинг 1. Код, обходящий несовместимости версий
#!/usr/bin/python
# -*- coding: utf-8 -*-
import sys

if len( sys.argv ) > 1:  n = int( sys.argv[ 1 ] )
else: n = int( input( "число?: " ) )

try:               # Python 2
    factorial = lambda z: reduce( lambda x, y: x * y, range( 1, z + 1 ) )
    print( "n={} => n!={}".format( n, factorial( n ) ) )
except NameError:  # Python 3
    import functools
    factorial = lambda z: functools.reduce( lambda x, y: x * y,
                                            range( 1, z + 1 ) )
    print( "n={} => n!={}".format( n, factorial( n ) ) )

В листинге 1 представлен один из вариантов рекурсивного вычисления факториала. Несовместимость здесь вызвана тем, что в Python 2 функция reduce() - встроенная, а в Python 3 она относится к модулю functools, и её вызов по имени возбуждает исключение NameError. Перехват этого исключения в операторе try и обеспечивает совместимость версий.

Для детального анализа различных случаев несовместимости можно использовать тестовое приложение из листинга 2 (полный код находится в файле 3vs2.py в архиве python_intro.tgz в разделе "Материалы для скачивания").

Листинг 2. Причины потенциальной несовместимости версий
#!/usr/bin/python
# -*- coding: utf-8 -*-

import functools
import sys
import re

# кортеж тестируемых выражений языка ('' - разделитель группы):
prgs = '', \
       '10 / 3', '10 / 3.', '10 / float( 3 )', \
       '', \
       'type( "строка UTF-8" )', 'len( "строка UTF-8" )', \
       '', \
       'type( range( 5 ) )', 'range( 5 )', 'list( range( 5 ) )', \
       'type( xrange( 5 ) )', 'map( int, [ "1", "2", "3" ] )', \
       'list( map( int, [ "1", "2", "3" ] ) )', \
       '', \
       'reduce( lambda x, y: x + y, range( 5 ) )', \

def arg_exit( arg, cod=1 ):
    print( 'недопустимый параметр командной строки: {} !'.format( arg ) )
    sys.exit( cod )

# разбор диапазонов
def dialist( ls ):
    lim = re.split( '\D+', ls )
    print( lim )
    if len( lim ) != 2: arg_exit( ls, 2 )
    try:
        ilim = list( map( int, lim ) )
    except ValueError: arg_exit( ls, 3 )
    print( '===', ilim )
    args = list( range( min( ilim ), max( ilim ) + 1 ) )
    return args

def arglist( la ):
    lb = []
    for x in la:                      # разнос параметров через ,
         lb += re.split( ',', x )
    args = []
    for x in lb:                      # разбор параметров
        try:
            args.append( int( x ) )
        except ValueError:
            args += dialist( x )
    return args


print( 'версия Python {}.{}.{} на платформе {}'. \
       format( sys.version_info.major, sys.version_info.minor,
               sys.version_info.micro, sys.platform ) )
if len( sys.argv ) == 1: # запуск без параметров
    r = functools.reduce( lambda x, y: x + y, 
    map( lambda x: int( len( x ) == 0 ), prgs ) )
    args = list( range( r + 1 ) )
else:                    # обработка списка параметров
    args = arglist( sys.argv[ 1: ] )
sarg = set( args )       # удаление дубликатов

n = 0
for x in prgs:
    if len( x ) == 0:
        n += 1
        if n not in args: continue
        print( '{:02d}: -------------------------------------'.format( n ) )
    else:
        if n not in args: continue
        try:
            print( '{} = {}'.format( x, eval( x ) ) )
        except ( NameError, SyntaxError ) as err:
            print( '{} => {} !'.format( x, err ) )
        except:
            print( '{} => непонятное исключение !'.format( x ) )

В листинге 2 анализируемые (потенциально несовместимые по версиям) синтаксические конструкции выписаны как элементы кортежа prgs, и вы можете самостоятельно добавлять туда "подозрительные" выражения в форме текстовых строк, которые программа попытается выполнить. Некоторая громоздкость приложения связана с тем, что все выражения в prgs разделены на тематические группы (разделителем группы является пустая строка ''), а программа позволяет параметрами запуска выбрать только обрабатываемые группы (списком или диапазоном), так как с ростом числа тестируемых конструкций вывод становится слишком объёмным.

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

Для Python версии 2:

 $ python 3vs2.py
версия Python 2.7.3 на платформе linux2
01: -------------------------------------
10 / 3 = 3
10 / 3. = 3.33333333333
10 / float( 3 ) = 3.33333333333
02: -------------------------------------
type( "строка UTF-8" ) = <type 'str'>
len( "строка UTF-8" ) = 18
03: -------------------------------------
type( range( 5 ) ) = <type 'list'>
range( 5 ) = [0, 1, 2, 3, 4]
list( range( 5 ) ) = [0, 1, 2, 3, 4]
type( xrange( 5 ) ) = <type 'xrange'>
map( int, [ "1", "2", "3" ] ) = [1, 2, 3]
list( map( int, [ "1", "2", "3" ] ) ) = [1, 2, 3]
04: -------------------------------------
reduce( lambda x, y: x + y, range( 5 ) ) = 10

Для Python версии 3:

$ python3 3vs2.py
версия Python 3.2.3 на платформе linux2
01: -------------------------------------
10 / 3 = 3.3333333333333335
10 / 3. = 3.3333333333333335
10 / float( 3 ) = 3.3333333333333335
02: -------------------------------------
type( "строка UTF-8" ) = <class 'str'>
len( "строка UTF-8" ) = 12
03: -------------------------------------
type( range( 5 ) ) = <class 'range'>
range( 5 ) = range(0, 5)
list( range( 5 ) ) = [0, 1, 2, 3, 4]
type( xrange( 5 ) ) => name 'xrange' is not defined !
map( int, [ "1", "2", "3" ] ) = <map object at 0xb7428eec>
list( map( int, [ "1", "2", "3" ] ) ) = [1, 2, 3]
04: -------------------------------------
reduce( lambda x, y: x + y, range( 5 ) ) => name 'reduce' is not defined !

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

Заключение

В этой вводной статье цикла были рассмотрены различия в синтаксисе и семантике между версиями Python 2 и 3. На практических примерах было показано, какого рода несовместимости можно ожидать, и как можно их избежать при разработке кода.

Во всём дальнейшем изложении мы будем, по возможности, показывать код, запуск которого даёт одинаковые результаты как в версии 2, так и в версии 3. Также будут демонстрироваться сравнительные результаты для различных версий (и, иногда, для различных операционных систем: Linux и Windows). На сегодня, во время продолжающейся миграции от версии 2 к версии 3, подобное сравнение может оказаться крайне полезным.

Но в некоторых случаях различия между версиями могут быть настолько существенными, что написание переносимого кода крайне усложняется (например, при создании межязыковых интерфейсов Python с C/C++).

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


Ресурсы для скачивания


Похожие темы


Комментарии

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

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=40
Zone=Open source
ArticleID=953964
ArticleTitle=Тонкости использования языка Python: Часть 1. Версии и совместимость
publish-date=11212013