воскресенье, 30 июня 2013 г.

Python: примеры и тесты, часть 1 - функции

На сегодня, по моему мнению, 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



Комментариев нет: