На сегодня, по моему мнению, Python составляет лучший "тендем" разработчику C/C++ в Linux, из числа интерпретирующих языков. Ещё лет 5 назад я бы на должность такого кандидата называл бы Perl. Подобные инструменты крайне необходим в крупных проектах, начиная от написания скриптов и тестов, и до полновесной реализации проекта (в частности, в качестве подобных вспомогательных инструментов в разном качестве интересны, например, JavaScript и Lua).
Но многие стороны использования Python достаточно поверхностно описаны в документации и публикациях, примеры кода их иллюстрирующие, временами, старые или неработающие ("писано с головы" ... не на клавиатуре проверялось). Поэтому по уточнению отдельных аспектов использования Python мне потребовалось написать (или воссоздать из существующих) примеры кода и тесты его применимости. И мне показалось, что некоторые из них могут быть любопытны и другим пользователям...
P.S. Меня совершенно не интересует использование Python в WEB-разработках, где может использоваться с равным успехом
любой язык или технология: Perl, JavaScript, ... CGI-интерфейс, Ajax и другие всякие штучки. Это совершенно особая песня. Речь идёт только о использовании Python в "классическом" программировании.
Хронологическое развитие темы Python развёрнуто в
форумном обсуждении форумном обсуждении, но там оно, как и полагается такому обсуждению "в прямом эфире" скомкано и рвано... Кроме того, в том объёмном обсуждении затронуты другие сопутствующие темы: обсуждение публикаций и описаний языка, учебных курсов, интегрированных сред разработки (IDE), облегчающих разработку в Python. Здесь же всё это, как "рутинные" вопросы, будет отброшено, и предполагается сделать некоторую упорядоченную подборку по предмету "примеры и тесты", как и сказано в заголовке. И дополнять их по мере накопления новых ... примеров и тестов...
Собственно, для программирования в UNIX/Linux меня интересовало всего несколько аспектов Python, по моему мнению явно недостаточно освещённых в литературе. А именно:
- "ручное" написание собственных модулей Python на языках C/C++;
- использование для этого специализированных инструментальных средств, таких как Swig ... и других полезных инструментов;
- обработка опций командной строки (модуль getopt) для построения консольных приложений в UNIX-стиле;
- реализация параллельных потоков (причём как в технике "низкого уровня" - модуль thread, так и технике "высокого уровня" - модуль threading);
- какие доступные механизмы синхронизаций потоков как в том, так и в другом варианте (поскольку как без синхронизации цена параллелизму - ноль);
- использование fork() и других механизмов для порождения дочерних процессов;
- работа с сигналами UNIX (модуль signal) ...
- средства метапрограммирования в Python: метаклассы, декораторы...
Ну и, возможно, некоторые другие...
Функциональное программирование.
О функциональном стиле программирования в Python написано много: практически ни одно руководство по языку не обходится без главы с таким названием... Но всё написанное базируется на очень ограниченном числе публикаций от одного-двух авторов. Представляется так, что ... "слухи о функциональном программировании на Python сильно преувеличены". Функционально программировать лучше, пожалуй, на языках, предназначенных специально для функционального программирования: Lisp, Haskell, Scheme... Но в Python можно с успехом строить фрагменты в стиле функционального программирования, с пользой для итогового результата. Это достаточно сильно напоминает то, как функциональный стиль использовался в языке APL. И, конечно, как всегда в функциональном программировании, с широчайшим применением рекурсии, часто и для замены итерационных вычислений.
Традиционно самый распространённый приём иллюстрации рекурсии - это вычисление факториала (это как программа "Hello world!" в иллюстрации любого языка программирования). Но, не исключено, что факториал и есть обоснованно удачный пример в виду своей простоты. Вот такой пример:
#!/usr/bin/python
# -*- coding: utf-8 -*-
from sys import *
arg = lambda : ( len( argv ) > 1 and int( argv[ 1 ] ) ) or \
int( input( "число?: " ) )
factorial = lambda x: ( ( x == 1 ) and 1 ) or x * factorial( x - 1 )
n = arg()
m = getrecursionlimit()
if n >= m :
print "глубина рекурсии превышает установленную в системе %d, \
переустановлено в %d" % \
( m, n + 1 )
setrecursionlimit( n + 1 )
print "n=%d => n!=%d" % ( n, factorial( n ) )
if getrecursionlimit() > m :
print "глубина рекурсии восстановлена в %d" % m
setrecursionlimit( m )
Собственно, самой программы и определениями функций здесь являются только две строки: обеспечивающая ввод аргумента вычисления arg, и осуществляющая вычисление факториала от этого аргумента:
$ ./fact2.py 10
n=10 => n!=3628800
$ ./fact2.py
число?: 10
n=10 => n!=3628800
Вся оставшаяся большая часть связана с тем, что я прочитал где-то в интернет-обсуждениях такую глупость: "... а вы попробуйте на Python вычислить факториал больше 1000 с соотвествующей глубиной рекурсии...". Попробуем:
$ ./fact2.py 10000
глубина рекурсии превышает установленную в системе 1000, переустановлено в 10001
n=10000 => n!=28462596809...000
глубина рекурсии восстановлена в 1000
Ну и показанноое число-результат (28462596809...000) будет занимать запись в 163
строки терминала. Но это огромное значение замечательно вычислилось исполняющей системой Python!
Хотя и без рекурсии реализация вычисления факториала в функциональном стиле выглядит красиво, используя встроенные функции высшего порядка, в данном случае reduce (это уже стиль языка APL):
factorial = lambda z: reduce( lambda x, y: x * y, range( 1, z + 1 ), 1 )
if len( sys.argv ) > 1: n = int( sys.argv[ 1 ] )
else: n = int( input( "число?: " ) )
print "n=%d => n!=%d" % ( n, factorial( n ) )
И снова проверяем:
$ ./fact3.py 5
n=5 => n!=120
$ ./fact3.py 50
n=50 => n!=30414093201713378043612608166064768844377641568960512000000000000
Функции высших порядков.
Функциональное программирование предоставляет возможности, недостижимые в императивном программировании (или достигаемые очень искусственными приёмами). В частности, это связано с возможностью динамического создания новых функциональных объектов "на лету". Вот некоторые из таких приёмов:
- Замыкание - когда из вызова возвращается объект-функция, конкретизированная параметрами образующего вызова;
- Функторы - параметризируемые (параметрами конструктора) объекты класса, который допускает функциональный вызов для экземпляров этого класса;
- Частичное применение функции - когда функция нескольких переменных фиксируется для частных значений некоторых из этих переменных (уменьшается размерность переменных функции);
- Карринг (англ. currying, или каррирование) — преобразование функции от многих аргументов в функцию, берущую свои аргументы по одному.
Проверяем как это работает:
#!/usr/bin/python
# -*- coding: utf-8 -*-
def addClosure( val1 ): # замыкание - closure
def closure( val2 ):
return val1 + val2
return closure
cl = addClosure( 3 ) # новая функция
class addFunctor: # эквивалентный предыдущему функтор
def __init__( self, val1 ):
self.val1 = val1
def __call__( self, val2 ):
return self.val1 + val2
fn = addFunctor( 3 ) # экземпляр класса addFunctor
from functools import partial # частичное применение функции
def addPart( a, b ): # функция 2-х переменных
return a + b
pr = partial( addPart, 3 ) # новая функция 1-й переменной
print cl( 2 ), fn( 2 ), pr( 2 ) # напечатает: 5 5 5
Каждый из 3-х вызовов в последнем операторе print синтаксически корректен, и возвратит своим значением 5.
Пример карринга (файл
curry1.py):
#!/usr/bin/python
# -*- coding: utf-8 -*-
def spam( x, y ) :
print u"x=%d, y=%d" % ( x, y )
spam1 = lambda x : lambda y : spam( x, y ) # новая функция
def spam2( x ) : # новая функция
def new_spam( y ) :
return spam( x, y )
return new_spam
spam1( 2 )( 3 )
spam2( 2 )( 3 )
$ ./curry1.py
x=2, y=3
x=2, y=3