Атрибуты
Мы видели, что объекты имеют атрибуты, а функция dir() возвращает список этих атрибутов. Иногда, однако, мы просто хотим определить наличие одного или более атрибутов. И если у объекта есть интересующий нас атрибут, мы часто хотим извлечь этот атрибут. Эти задачи решаются с помощью функций hasattr() и getattr(), как показано в следующем примере:
Листинг 31. Наличие атрибута; получение атрибута
>>> print hasattr.__doc__ hasattr(object, name) -> Boolean
Return whether the object has an attribute with the given name. (This is done by calling getattr(object, name) and catching exceptions.)
>>> print getattr.__doc__ getattr(object, name[, default]) -> value
Get a named attribute from an object; getattr(x, 'y') is equivalent to x.y. When a default argument is given, it is returned when the attribute doesn't exist; without it, an exception is raised in that case.
>>> hasattr(id, '__doc__') 1 >>> print getattr(id, '__doc__') id(object) -> integer
Return the identity of an object. This is guaranteed to be unique among simultaneously existing objects. (Hint: it's the object's memory address.)
Больше чем основы
Функции, включенные в itertools - залог хорошего начала. Как минимум, они стимулируют программистов на Python к использованию и комбинированию итераторов. В общем, широкое использование итераторов безусловно важно для будущего Python. Но, помимо уже включенных, имеются и другие функции, которые я бы рекомендовал для будущих редакций этого модуля. Ими можно воспользоваться немедленно - разумеется, если они затем будут включены, часть имен и интерфейсов может отличаться.
Одна категория, которая могла бы в общем показаться полезной- это функции, принимающие набор итерируемых аргументов, затем выдающие отдельные элементы из каждого итерируемого аргумента. В противоположность этому, izip() выдает кортежи элементов, а imap() выдает значения, вычисленные из основных элементов. Два очевидных решения, по моему мнению, это chain() и weave(). Первая функция, по существу, подобна конкатенации последовательностей (уместно отложенной). То есть где для простых последовательностей вы использовали бы, например:
for x in ('a','b','c') + (1, 2, 3): do_something(x)
для обычных итерируемых аргументов вы могли бы использовать:
for x in chain(iter1, iter2, iter3): do_something(x)
Питоновская реализация такова:
Листинг 3. Пример реализации chain()
for x in chain(iter1, iter2, iter3): do_something(x)
Вы также могли бы комбинировать итераторы, перемежая их. Встроенный синтаксис, чтобы сделать то же самое с последовательностями, отсутствует, однако сама weave() также прекрасно работает для конечных последовательностей. Возможная реализация приведена ниже (Магнус Лай Хетленд (Magnus Lie Hetland) привел похожую функцию на comp.lang.python):
Листинг 4. Пример реализации weave()
def weave(*iterables): "Intersperse several iterables, until all are exhausted" iterables = map(iter, iterables) while iterables: for i, it in enumerate(iterables): try: yield it.next() except StopIteration: del iterables[i]
Позвольте мне проиллюстрировать поведение weave(), поскольку оно может быть не сразу очевидно из этой реализации:
>>> for x in weave('abc', xrange(4), [10,11,12,13,14]): ... print x, ... a 0 10 b 1 11 c 2 12 13 3 14
Даже после того, как некоторые итераторы использованы, оставшиеся продолжают выдавать величины, пока в какой-то момент все доступное не будет выдано.
Я предложу еще одну возможную функцию itertools. Подход к проблеме в ней во многом позаимствован из функционального программирования. icompose() обладает определенной симметрией с рассмотренной выше ireduce(). Однако, если ireduce() передает в функцию (отложенную) последовательность величин и выдает каждый результат, icompose() применяет последовательность функций к возвращаемой величине каждой предшествующей функции. Вероятное использование ireduce() - передать последовательность событий в долгоживущий объект. icompose() может вместо этого последовательно передавать объект функциям-мутаторам, каждая из которых возвращает новый объект. Если первая - довольно традиционный для объектно-ориентированного программирования способ представления событий, вторая более характерна для подходов функционального программирования.
Ниже - возможная реализация icompose():
Листинг 5. Пример реализации icompose():
def icompose(functions, initval): currval = initval for f in functions: currval = f(currval) yield currval
Что такое функциональное программирование?
Лучше всего начать с труднейшего вопроса - а что, собственно, такое "функциональное программирование (FP)"? Один из возможных ответов - "это когда вы пишете на языке наподобие Lisp, Scheme, Haskell, ML, OCAML, Clean, Mercury или Erlang (или еще на некоторых других)". Этот ответ, безусловно, верен, но не сильно проясняет суть. К сожалению, получить четкое мнение о том, что же такое FP, оказывается очень трудно даже среди собственно функциональных программистов. Вспоминается притча о трех слепцах и слоне. Возможно также определить FP, противопоставив его "императивному программированию" (тому, что вы делаете на языках наподобие C, Pascal, C++, Java, Perl, Awk, TCL и на многих других - по крайнее мере, большей частью).
Хотя автор всеми силами приветствует советы со стороны тех, кто лучше него знает предмет, он мог бы приблизительно охарактеризовать функциональное программирование как обладающее как минимум несколькими из следующих свойств. В языках, называемых функциональными, хорошо поддерживаются нижеперечисленные подходы, а все прочие подходы поддерживаются плохо или не поддерживаются вовсе: Функции - объекты первого класса. Т.е., все, что можно делать с "данными", можно делать и с функциями (вроде передачи функции другой функции в качестве параметра).
Использование рекурсии в качестве основной структуры контроля потока управления. В некоторых языках не существует иной конструкции цикла, кроме рекурсии.
Акцент на обработке списков (lists, отсюда название Lisp - LISt Processing). Списки с рекурсивным обходом подсписков часто используются в качестве замены циклов.
"Чистые" функциональные языки избегают побочных эффектов. Это исключает почти повсеместно распространенный в императивных языках подход, при котором одной и той же переменной последовательно присваиваются различные значения для отслеживания состояния программы.
FP не одобряет или совершенно запрещает утверждения (statements), используя вместо этого вычисление выражений (т.е. функций с аргументами). В предельном случае, одна программа есть одно выражение (плюс дополнительные определения).
FP акцентируется на том, что должно быть вычислено, а не как.
Большая часть FP использует функции "высокого порядка" (функции, оперирующие функциями, оперирующими функциями).
Защитники функционального программирования доказывают, что все эти характеристики приводят к более быстрой разработке более короткого и безошибочного кода. Более того, высокие теоретики от компьютерной науки, логики и математики находят, что процесс доказательства формальных свойств для функциональных языков и программ много проще, чем для императивных.
Что такое интроспекция?
В повседневной жизни, интроспекция - это проявление самоанализа. Интроспекция основывается на изучении собственных мыслей, ощущений, мотивов и поступков. Великий философ Сократ провел большую часть своей жизни, занимаясь самоанализом и призывая своих сограждан-афинян следовать своему примеру. Он даже утверждал, что для него самого "непроанализированная жизнь не стоит существования" (за более подробной информацией о Сократе см. ссылки, приведенные в ).
В случае программирования интроспекция означает возможность изучать что-либо, чтобы определить, что это такое, что оно умеет и что может делать. Интроспекция предоставляет программистам огромные гибкость и контроль. Если вы хоть раз работали с языками программирования, которые поддерживают интроспекцию, вы, возможно, испытываете схожие чувства: "непроанализированный объект не стоит воплощения".
В этой статье рассмотрены интроспективные возможности языка программирования Python. Питоновская поддержка интроспекции пронизывает язык вглубь и вширь. В действительности, Python трудно представить без его интроспективных возможностей. Прочитав эту статью, вы сможете с легкостью "проникнуть в самое сердце" своих объектов Python.
Мы начнем наше изучение Питоновской интроспекции в самом общем виде, в каком только возможно, а затем окунемся в более сложные технологии. Возможно, кто-нибудь даже заявит, что характеристики, которые являются нашей отправной точкой, не заслуживают звания "интроспективных". Согласимся, что, попадают ли они или нет под определение интроспекции, остается спорным моментом. Цель этой статьи и наша единственная задача - найти ответы на интересующие вопросы.
Давайте приступим к нашему исследованию, используя Python интерактивно. При запуске Python из командной строки мы входим в оболочку Python, где можем вводить код Python и получать немедленный ответ от интерпретатора Python. (Команды, перечисленные в этой статье, выполнятся надлежащим образом для Python 2.2.2.
Возможно, вы получите другие результаты и ошибки, если используете более раннюю версию. Последнюю версию Python можно скачать с Web-сайта Python (см. ).)
Листинг 1. Запускаем интерпретатор Python в интерактивном режиме
$ python Python 2.2.2 (#1, Oct 28 2002, 17:22:19) [GCC 3.2 (Mandrake Linux 9.0 3.2-1mdk)] on linux2 Type "help", "copyright", "credits" or "license" for more information.
>>>
Запустив Python и посмотрев на его приглашение (>>>), вы, возможно, зададитесь вопросом, какие слова распознает Python. В большинстве языков программирования существуют зарезервированные, или ключевые слова, которые имеют специальное назначение в данном языке, и Python не является исключением. Возможно, вы также обратили внимание, что Python предлагает напечатать help для получения более подробной информации. Наверное, мы можем запросить у Python какую-нибудь справку о ключевых словах.
Что такое Python?
Python - свободно распространяемый, очень высокоуровневый интерпретируемый язык, разработанный Гвидо ван Россумом (Guido van Rossum). Он сочетает прозрачный синтаксис с мощной (но необязательной) объектно-ориентированной семантикой. Python доступен почти на всех существующих ныне платформах и обладает очень высокой переносимостью между платформами.
Документирование функций.
Хорошим стилем является документация каждой функции. Для этого сразу после заголовка поместите краткое описание функции, заключённое в тройные кавычки. Всё содержимое внутри тройных кавычек выводится как есть, в “сыром” виде. Такой способ позволяет легко понять назначение функции, прочитав исходный текст или воспользовавшись специальным сервером документации Питона.
Дополнительная информация о модулях.
Модули могут содержать в себе кроме определений функций и некоторый исполняемый код. Этот код не входит ни в какую функцию и исполняется интерпретатором, когда данный модуль подключается ключевым словом import. В исполняемом коде модуля можно использовать любые переменные, которые внутри модуля объявляются глобальными. Но каждый модуль имеет собственную таблицу переменных, так что случайное изменение таких переменных вне модуля исключено. Такой механизм обеспечивает стабильность работы модуля и соответствует принципу сокрытия информации.
Модули могут импортировать другие модули. Желательно, чтобы операторы import стояли вначале модуля, так как это помогает сразу понять, какие файлы необходимы для его правильной работы.
Из модуля можно импортировать не все, а только необходимые в данной ситуации функции(или иные объекты), тогда оператор import будет выглядеть так:
>>> from fibo import fib, fib2 >>> fib(500) 1 1 2 3 5 8 13 21 34 55 89 144 233 377
При этом стоит отметить, что к другим функциям модуля обратиться будет невозможно, так ка модуль fibo будет неопределён. Для импортирования из модуля всех функций, кроме тех, имена которых начинаются на _, можно использовать import следующим образом:
>>> from fibo import * >>> fib(500) 1 1 2 3 5 8 13 21 34 55 89 144 233 377
Другие функции высшего порядка
Помимо фундаментальной функции curry(), в модуль functional включает ряд интереснейших функций высшего порядка. Более того, не составляет большого труда самому написать свои собственные функции высшего порядка - с помощью или без этого модуля. Во всяком случае, можно воспользоваться идеями, представленными в нем.
По большей части функции высшего порядка ведут себя как "усовершенствованные" версии стандартных функций map(), filter()
и reduce(). В большинстве случаев они действуют согласно следующему правилу: "принять функцию или функции и некоторые списки в качестве параметров, затем применить функцию (функции) к списку параметров". Это правило предоставляет потрясающие возможности для программирования. Другой принцип "принять набор функций и создать функцию, комбинирующую их функциональность". И опять-таки, возможны многочисленные вариации. Давайте взглянем, что предоставляет functional.
Функции sequential() и also() создают функцию, основанную на последовательность других функций. Функции-компоненты затем могут быть вызваны с одинаковым аргументом (аргументами). Главное различие между этими двумя функциями заключается в том, что в sequential() список функций принимается в качестве первого аргумента, а also() принимает список аргументов (каждый из которых должен быть функцией) переменной длины. В большинстве случаев их используют ради побочных эффектов составляющих функций, однако sequential() позволяет опционально задать, результат какой функции вернуть как комбинированное значение:
#---- Sequential calls to functions (with same args) ----#
>>> def a(x):
... print x,
... return "a"
...
>>> def b(x):
... print x*2,
... return "b"
...
>>> def c(x):
... print x*3,
... return "c"
...
>>> r = also(a,b,c)
>>> r
>>> r(5)
5 10 15
'a'
>>> sequential([a,b,c],main=c)('x')
x xx xxx
'c'
Функции disjoin() и conjoin() схожи с sequential() и also() в том смысле, что они также создают новые функции, которые применяют параметр(ы) к нескольким составляющим функциям. Но disjoin()
выясняет, возвращает ли хотя бы одна из составляющих функций "истину" (true), а conjoin() выясняет, возвращают ли все
функции "истину". При этом, когда это возможно, логика "короткого замыкания", поэтому при их вызове часть побочных эффектов может не проявиться. joinfuncs()
похожа на also(), но, в отличие от нее, возвращает кортеж результатов составляющих функций, а не выбирает одно значение.
В то время как вышеуказанные функции вызывают много функций с одинаковыми параметрами, функции any(), all() и none_of()
позволяют вызывать одну и ту же функцию для каждого значения из списка. В общем случае они подобны встроенным функциям map(), filter()
и reduce(). Но, в отличие от последних, эти функции задают булевы (логические) вопросы касательно набора возвращаемых величин. Например,
#--------- Ask about collections of return values -------#
>>> from functional import *
>>> isEven = lambda n: (n%2 == 0)
>>> any([1,3,5,8], isEven)
1
>>> any([1,3,5,7], isEven)
0
>>> none_of([1,3,5,7], isEven)
1
>>> all([2,4,6,8], isEven)
1
>>> all([2,4,6,7], isEven)
0
Особый интерес для тех, кто неравнодушен к математике, представляет функция высшего порядка compose(). Композиция из нескольких функций формирует цепочку, направляя возврат одной функции на вход следующей. Программист, комбинирующий несколько функций, должен следить за тем, чтобы выход и вход соответствовали друг другу - но ведь это так в любом случае, если программист использует возвращаемое значение. Ниже приведен пример, поясняющий сказанное:
#----------- Creating compositional functions -----------#
>>> def minus7(n): return n-7
...
>>> def times3(n): return n*3
...
>>> minus7(10)
3
>>> minustimes = compose(times3,minus7)
>>> minustimes(10)
9
>>> times3(minus7(10))
9
>>> timesminus = compose(minus7,times3)
>>> timesminus(10)
23
>>> minus7(times3(10))
23
Другие способы решения проблем с помощью магии
Существует область программирования, где классы зачастую более важны, чем экземпляры. Например, декларативные мини-языки (declarative mini-languages) - это библиотеки Python, программная логика которых выражена непосредственно в объявлении класса. Дэвид рассматривает их в своей статье "Создание декларативных мини-языков" (). В подобных случаях использование метаклассов для воздействия на процесс создания класса может быть весьма эффективным.
Одной из декларативных библиотек, основанных на классах, является gnosis.xml.validity. В этой структуре вы объявляете ряд "классов допустимости" ("validity classes"), которые описывают набор ограничений для допустимых документов XML. Эти объявления очень близки к тем, что содержатся в описаниях типа документа (DTDs). Например, документ "диссертация" может быть сконфигурирован с помощью следующего кода:
Листинг 10. Правила gnosis.xml.validity в simple_diss.py
from gnosis.xml.validity import * class figure(EMPTY): pass class _mixedpara(Or): _disjoins = (PCDATA, figure) class paragraph(Some): _type = _mixedpara class title(PCDATA): pass class _paras(Some): _type = paragraph class chapter(Seq): _order = (title, _paras) class dissertation(Some): _type = chapter
Если попытаться создать экземпляр dissertation без надлежащих подэлементов, возбуждается исключение, описывающее ситуацию; подобное имеет место для каждого подэлемента. Правильные подэлементы будут автоматически сгенерированы из более простых аргументов, если существует только один непротиворечивый способ "достроить" тип до корректного состояния.
Хотя классы допустимости часто (неформально) базируются на предварительно существующем DTD, экземпляры этих классов печатаются как внеконтекстные (unadorned) фрагменты документа XML, например:
Листинг 11. Создание документа с базовым классом допустимости
>>> from simple_diss import * >>> ch = LiftSeq(chapter, ('It Starts','When it began')) >>> print ch <chapter><title>It Starts</title> <paragraph>When it began</paragraph> </chapter>
Используя метакласс для создания классов допустимости, мы можем генерировать DTD из самих объявлений класса (и при этом добавить дополнительный метод в эти классы):
Листинг 12. Использование метаклассов во время импорта модуля
>>> from gnosis.magic import DTDGenerator, \ ... import_with_metaclass, \ ... from_import >>> d = import_with_metaclass('simple_diss',DTDGenerator) >>> from_import(d,'**') >>> ch = LiftSeq(chapter, ('It Starts','When it began')) >>> print ch.with_internal_subset() <?xml version='1.0'?> <!DOCTYPE chapter [ <!ELEMENT figure EMPTY> <!ELEMENT dissertation (chapter)+> <!ELEMENT chapter (title,paragraph+)> <!ELEMENT title (#PCDATA)> <!ELEMENT paragraph ((#PCDATA|figure))+> ]> <chapter><title>It Starts</title> <paragraph>When it began</paragraph> </chapter>
Пакету gnosis.xml.validity ничего неизвестно о DTD и внутренних подмножествах. Эти концепции и возможности всецело представлены метаклассом DTDGenerator без внесения каких-либо изменений в gnosis.xml.validity или simple_diss.py. DTDGenerator не подставляет свой собственный метод .__str__() в классы, которые он создает - вы по-прежнему можете вывести внеконтекстный фрагмент XML - но метакласс мог бы легко модифицировать подобные магические методы.
Еще о функциональном программировании на Python
Автор: David Mertz, Ph.D., Applied Metaphysician, Gnosis Software, Inc
Перевод: Intersoft Lab
Эта статья продолжает серию статей о функциональном программирования (ФП) на Python. В ней демонстрируется несколько достаточно сложных концепций ФП. Читателю окажется полезным введение в различные подходы программного решения задач.
Фабрики базовых итераторов
Все функции в модуле itertools могут быть легко реализованы на чистом Python как генераторы. Основной смысл этого модуля в Python 2.3+ - обеспечить стандартное поведение и имена для некоторых полезных функций. Хотя программисты могли бы написать свои собственные версии, на практике каждый создал бы слегка несовместимые вариации. Помимо этого, еще одна причина - эффективная реализация итераторных комбинаторов на С. Использование функций itertools будет заметно быстрее, чем написание своих собственных комбинаторов. В стандартной документации показаны эквивалентные реализации на чистом Python для каждой функции itertools, так что нет необходимости повторять их в этой статье.
Функции в модуле itertools настолько базовые - и достаточно четко поименованные - что, вероятно, имеет смысл импортировать все имена из этого модуля. Функция enumerate(), например, вполне могла бы существовать в itertools, но вместо этого является встроенной функцией в Python 2.3+. В частности, вы можете легко выразить enumerate() в терминах функций itertools:
from itertools import * enumerate = lambda iterable: izip(count(), iterable)
Давайте рассмотрим несколько функций itertools, которые не используют другие итераторы в качестве базиса, а просто создают итераторы "с нуля". times() возвращает итератор, который выдает идентичный объект множество раз; сама по себе эта возможность довольно удобна, но по-настоящему она хороша при избыточном использовании xrange() и индексной переменной для простого повторения действия. То есть вместо:
for i in xrange(1000): do_something()
Вы можете теперь использовать более нейтральное:
for _ in times(1000): do_something()
Если второй аргумент не передается в times(), она просто повторно выдает None. Функция repeat() подобна times(), но неограниченно возвращает тот же самый объект. Этот итератор удобен либо если у цикла имеется независимое условие break, либо в комбинаторах, как izip() и imap().
Функция count() похожа на помесь repeat() и range(). count() неограниченно возвращает последовательно идущие целые (начиная с факультативного аргумента). Однако, с учетом того, что в настоящий момент count() корректно не поддерживает автоматическое преобразование к long при переполнении, вы могли бы с равным успехом по-прежнему использовать xrange(n,sys.maxint); она не является неограниченной буквально, но для большинства целей приводит к тому же. Как и repeat(), count() особенно удобна в других итераторных комбинаторах.
Форматированный ввод/вывод.
В Питоне вы можете считывать данные либо с клавиатуры, либо с файла и выводить на монитор или также в файл. Разницы для программиста между файлом и дисплеем нет никакой(они представлены в виде потока байт). В файле обычно нужно сохранять те значения, которые можно впоследствие вновь использовать.
Форматированный ввод/вывод – это совокупность операций, обеспечивающая ввод/вывод высокого уровня переменных с применением определённого формата ввода/вывода.
В Питоне имеется несколько способов форматированного ввода/вывода. Самый простой из них – оператор print, печатающий переменные и строковые константы, применяя формат по умолчанию. Другой простой способ вывода данных на дисплей – функция str(), которая выводит любую переменную, используя также формат по умолчанию. Есть также функция repr(), которая выводит данные в машинном(неформатированном) виде. Некоторые типы переменных, которые не могут должным образом отформатироваться функцией str(), обрабатываются схоже с repr(). Пример использования функций str() и repr():
>>> s = 'Hello, world.' >>> str(s) 'Hello, world.'
>>> `s`#Такое выражение значит то же, что и repr(s) "'Hello, world.'"#Машинный формат >>> str(0.1)#Переменная с точкой '0.1'
>>> `0.1`# repr(0.1) '0.10000000000000001'#Ошибка округления чисел с точкой >>> x = 10 * 3.25 >>> y = 200 * 200 >>> s = 'Значение x равно' + `x` + ', а y равен ' + `y` + '...'#Использование операций склеивания
... #строк для форматирования >>> print s Значение x равно 32.5, а y равен 40000...
>>> #Заключение переменных в кавычки работает для всех типов данных ... p = [x, y] >>> ps = repr(p) >>> ps '[32.5, 40000]'
>>> # Окружение строки дополнительными кавычками ... hello = 'hello, world\n' >>> hellos = `hello` >>> print hellos 'hello, world\n'
>>> # Можно заключать в кавычки и константные списки ... `x, y, ('Linux', 'BSD')` "(32.5, 40000, ('Linux', 'BSD'))"
Для форматированного вывода удобно использовать выравнивание по краю. Для этой цели в Питоне предусмотрены следующие функции модуля string: string.rjust(), string.ljust() и string.center(). Эти функции возвращают выровненную строку по правому и левому краю, а также по центру до определённого количества символов. Они принимают два параметра: саму строку и количество символов, зарезервированных под строку(если строка длинее, то она выводится как есть, а для удаления лишних символов воспользуйтесь “string.ljust(x, n)[0:n]#”). На примере это выглядит так:
>>> import string >>> for x in range(1, 11): ... print string.rjust(`x`, 2), string.rjust(`x*x`, 3), ... # В предыдущем операторе print была поставлена в конце запятая, перевода строки нет: ... print string.rjust(`x*x*x`, 4) ... 1 1 1
2 4 8
3 9 27
4 16 64
5 25 125
6 36 216
7 49 343
8 64 512
9 81 729
10 100 1000
Для этой же цели можно использовать оператор print со строкой формата, аналогичной Си:
>>> for x in range(1,11): ... print '%2d %3d %4d' % (x, x*x, x*x*x)# %d означает целое число ...
1 1 1
2 4 8
3 9 27
4 16 64
5 25 125
6 36 216
7 49 343
8 64 512
9 81 729
10 100 1000
Очевидно, что число после символа процента определяет количество символов для выравнивания. Спецификатор после процента обозначает тип числа:
%d | Целое число |
%f | Число с точкой |
%s | Строка |
%c | Символ |
%e | Число в научном формате |
>>> import string >>> string.zfill('12', 5) '00012'
>>> string.zfill('-3.14', 7) '-003.14'
>>> string.zfill('3.14159265359', 5) '3.14159265359'
Если использовать оператор print, то можно для той же цели использовать конструкцию “%ширина_поля.число_знаков_после_запятой”:
>>> import math >>> print 'Значение числа Пи приблизительно %5.3f.' % math.pi Значение числа Пи приблизительно 3.142.
Форматировать можно не только строки, но и другие объекты, например, списки, константные списки, заметьте, что в качестве спецификаций форматирования необходимо использовать только константные списки:
>>> table = {'Иван': 4127, 'Жека': 4098, 'Илья': 7678} >>> for name, phone in table.items(): ... print '%-10s ==> %10d' % (name, phone) ...
Иван ==> 4098
Жека ==> 7678
Илья ==> 4127
Для оператора print можно передавать аргументы не по порядку в строке формата, а по именам переменных, описанных в строке формата следующим образом “%(имя_переменной)тип_переменной”:
>>> table = {'Sjoerd': 4127, 'Jack': 4098, 'Dcab': 8637678} >>> print 'Jack: %(Jack)d; Sjoerd: %(Sjoerd)d; Dcab: %(Dcab)d' % table Jack: 4098; Sjoerd: 4127; Dcab: 8637678
Функции.
В современной теории информатики ключевым является понятие функции – небольшого модуля программного кода, выполняющего определённые действия и обособленного от основного кода программы. Основное достоинство использования функций – это возможность повторного использования программного кода, т.е вы можете вызывать функцию многократно не только в той программе, где она была написана, но, возможно, и в других программах, другими людьми и для других целей. Вы и до этого использовали много раз функции, функции написанные разработчиками Питона, которые очень универсальны и допускают использование в программах различного типа. При этом, вам совершеннно не нужно знать, как, например, работает функция range, вы просто используете её, не заботясь, кем и как она была написана. Этот принцип сокрытия информации позволяет легко использовать стандартные функции, не зная деталей их работы. В Питоне реализована исчерпывающая поддержка функций. В Питоне функция определяется ключевым словом def, имени функции, затем в скобках идут некие параметры, разделяемые запятой, передаваемые в функцию из программы. К этим параметрам можно обращаться внутри функции по их именам. Впрочем, функция может не принимать никаких параметров, тогда в скобках не нужно ничего писать.После определения функции к ней можно обращаться по имени из любого места программы, передавая ей регламентированные в определении параметры:
>>> def fib(n): # Функция, выводящая на экран числа Фибоначчи, не превосходящие n ... """Числа Фибоначчи""" ... a, b = 0, 1 ... while b < n: ... print b, ... a, b = b, a+b ...
>>> # Теперь функцию можно вызвать ... fib(2000)
Числа Фибоначчи 1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 1597
Функции могут не только принимать параметры, но и возвращать результат своей работы.(Это похоже на отношения начальника и подчинённого: программа вызывает функцию, передавая в неё некие параметры, и считывает результат работы с этими параметрами).
Возврат значения из функции в вызывающую программу осуществляется посредством оператора return.
>>> def fib(n): # Функция, возвращающая числа Фибоначчи, не превосходящие n ... result = [1]#Этот список будет содержать числа Фибоначчи ... a, b = 0, 1 ... while b < n: ... print b, ... a, b = b, a+b ... result.append(b);#Вставление в результативный список очередного числа ... return result #Возвращение результата ... >>> # Теперь функцию можно вызвать ... fib(2000) Результат выполнения: [1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 1597]
В определении функций есть некоторые нюансы. Рассмотрим, к примеру, функцию range(). Её можно вызвать в 3-х разных формах – с один параметром, с двумя и с тремя. Для организации такого поведения совсем необязательно описывать три различные функции, можно применить параметры по умолчанию:
def ask_ok(prompt, retries=4, complaint='Yes or no, please!'): while 1: ok = raw_input(prompt) if ok in ('д', 'да', 'yes'): return 1 if ok in ('н', 'нет', 'no', 'nop'): return 0 retries = retries - 1 if retries < 0: raise IOError, 'refusenik user' print complaint или в таком виде: i = 5
def f(arg=i): print arg
i = 6 f()#Выведет не 6, а 5
Механизм параметров по умолчанию действует так: если переменная задана при вызове функции, как её параметр, то в функцию передаётся именно это значение, иначе в функцию передаётся значение по умолчанию.
Внимание: значение по умолчанию оценивается лишь один раз, это играет роль при задании значения по умолчанию спискам, например:
def f(a, L=[]): L.append(a) return L
print f(1) print f(2) print f(3)
Результат работы программы: [1]
[1, 2]
[1, 2, 3]
Если вам нужно, чтобы параметры по умолчанию передавались раздельно, то используйте следующую форму:
def f(a, L=None): if L is None: L = [] L.append(a) return L
Функции как объекты первого класса
Приведенные примеры уже засвидетельствовали, хотя и неочевидным образом, статус функций как объектов первого класса в Python. Дело в том, что, создав объект функции оператором lambda, мы произвели чрезвычайно общее действие. Мы имели возможность привязать наш объект к именам pr и namenum в точности тем же способом, как могли бы привязать к этим именам число 23 или строку "spam". Но точно так же, как число 23 можно использовать, не привязывая ни к какому имени (например, как аргумент функции), мы можем использовать объект функции, созданный lambda, не привязывая ни к какому имени. Функция в Python - всего лишь еще одно значение, с которым можно что-то сделать.
Главное, что мы делаем с нашими объектами первого класса - передаем их во встроенные функции map(), reduce() и filter(). Каждая из этих функций принимает объект функции в качестве первого аргумента. map() применяет переданную функцию к каждому элементу в переданном списке (списках) и возвращает список результатов. reduce() применяет переданную функцию к каждому значению в списке и ко внутреннему накопителю результата; например, reduce(lambda n,m:n*m, range(1,10)) означает 10! (факториал 10 - умножить каждый элемент на результат предыдущего умножения). filter() применяет переданную функцию к каждому элементу списка и возвращает список тех элементов исходного списка, для которых переданная функция вернула значение истинности. Мы также часто передаем функциональные объекты нашим собственным функциям, но чаще некоторым комбинациям вышеупомянутых встроенных функций.
Комбинируя три этих встроенных FP-функции, можно реализовать неожиданно широкий диапазон операций потока управления, не прибегая к утверждениям (statements), а используя лишь выражения.
Функции высшего порядка: частичное вычисление функций - карринг (currying)
Три наиболее общих функций высшего порядка встроены в Python: map(), reduce()
и filter(). Эти функции используют в качестве (некоторых) своих параметров другие функции - вот почему мы называем их функциями высшего порядка. Другие функции высшего порядка (но не эти три) возвращают объекты-функции (function objects).
Python всегда предоставлял программистам возможность создавать свои собственные функции высшего порядка благодаря полноправному статусу функций как объектов. Ниже в качестве иллюстрации приведен простой пример:
#----------- Trivial Python function factory ------------#
>>> def foo_factory():
... def foo():
... print "Foo function from factory"
... return foo
...
>>> f = foo_factory()
>>> f()
Foo function from factory
Программа Xoltar Toolkit, о которой я упоминал в предыдущих статьях, содержит замечательный набор функций высшего порядка. Большинство этих функций, предоставляемых модулем functional, имеются во множестве традиционных функциональных языках программирования, и их полезность проверена многолетним использованием. Пожалуй, наиболее известная и важная функция высшего порядка традиционно называется curry(). Она названа в честь логика Хаскелла Карри (Haskell Curry), чьим именем назван уже упоминавшийся язык программирования. В основе карринга лежит допущение о том, что (почти) любую функцию можно рассматривать как частично вычисляемую функцию одного аргумента. Для того, чтобы эта идея работала, необходимо чтобы значение, возвращаемое функцией, само могло быть функцией, но возвращаемые функции должны быть уже или ближе к завершению .
Этот механизм подобен замыканию, о котором я рассказывал в предыдущей статье - каждый вызов каррированой функции добавляет больше данных, необходимых для окончательного вычисления (данные прикрепляются к процедуре). Проиллюстрируем сначала карринг очень простым примером на Haskell, а затем повторим тот же пример на Python с помощью модуля functional:
#------------- Currying a Haskell computation -----------#
computation a b c d = (a + b^2+ c^3 + d^4)
check = 1 + 2^2 + 3^3 + 5^4
fillOne = computation 1 -- specify "a"
fillTwo = fillOne 2 -- specify "b"
fillThree = fillTwo 3 -- specify "c"
answer = fillThree 5 -- specify "d"
-- Result: check == answer == 657
А теперь на Python:
#------------- Currying a Python computation ------------#
>>> from functional import curry
>>> computation = lambda a,b,c,d: (a + b**2 + c**3 + d**4)
>>> computation(1,2,3,5)
657
>>> fillZero = curry(computation)
>>> fillOne = fillZero(1) # specify "a"
>>> fillTwo = fillOne(2) # specify "b"
>>> fillThree = fillTwo(3) # specify "c"
>>> answer = fillThree(5) # specify "d"
>>> answer
657
Приведем еще один пример, подтверждающий, что между каррингом и замыканием много общего. Для этого, используя curry(), перепишем простую программу расчета налога, код которой можно найти в предыдущей статье:
#------------ Python curried tax calculations -----------#
from functional import *
taxcalc = lambda income,rate,deduct: (income-(deduct))*rate
taxCurry = curry(taxcalc)
taxCurry = taxCurry(50000)
taxCurry = taxCurry(0.30)
taxCurry = taxCurry(10000)
print "Curried taxes due =",taxCurry
print "Curried expression taxes due =", \
curry(taxcalc)(50000)(0.30)(10000)
В отличие от замыкания, при использовании curry( ) необходимо заполнять параметры в определенном порядке (слева направо). Но заметьте, в модуль functional также включен класс rcurry(), для которого отсчет начинается с другого конца (справа налево).
Обратите внимание на второй оператор print
в этом примере - с одной стороны, это всего лишь тривиальное синтаксическое изменение - можно было бы просто вызвать taxcalc(50000,0.30,10000). Но с другой стороны, благодаря этому становится понятным идея о том, что каждая функция может быть функцией всего одного аргумента - весьма неожиданная идея для тех, кто с эти незнаком.
Функция dir()
Несмотря на то, что относительно легко найти и импортировать модуль, не так-то просто запомнить, что каждый модуль содержит. И вряд ли вам захочется всякий раз смотреть исходный код, чтобы это выяснить. К счастью, Python предоставляет способ определения содержимого модулей (и других объектов) с помощью встроенной функции dir().
Функция dir(), вероятно, наиболее известная из всех интроспективных механизмов Python. Она возвращает отсортированный список имен атрибутов для любого переданного в нее объекта. Если ни один объект не указан, dir() возвращает имена в текущей области видимости. Давайте применим dir() к нашему модулю keyword и посмотрим, что она покажет:
Листинг 16. Атрибуты модуля keyword
>>> dir(keyword) ['__all__', '__builtins__', '__doc__', '__file__', '__name__', 'iskeyword', 'keyword', 'kwdict', 'kwlist', 'main']
А как насчет модуля sys, который мы рассматривали выше?
Листинг 17. Атрибуты модуля sys
>>> dir(sys) ['__displayhook__', '__doc__', '__excepthook__', '__name__', '__stderr__', '__stdin__', '__stdout__', '_getframe', 'argv', 'builtin_module_names', 'byteorder', 'copyright', 'displayhook', 'exc_info', 'exc_type', 'excepthook', 'exec_prefix', 'executable', 'exit', 'getdefaultencoding', 'getdlopenflags', 'getrecursionlimit', 'getrefcount', 'hexversion', 'last_traceback', 'last_type', 'last_value', 'maxint', 'maxunicode', 'modules', 'path', 'platform', 'prefix', 'ps1', 'ps2', 'setcheckinterval', 'setdlopenflags', 'setprofile', 'setrecursionlimit', 'settrace', 'stderr', 'stdin', 'stdout', 'version', 'version_info', 'warnoptions']
Без указания аргумента dir() возвращает имена в текущей области видимости. Заметьте, что keyword и sys присутствуют в этом списке, поскольку мы импортировали их ранее. Импортирование модуля добавляет имя этого модуля в текущую область видимости:
Листинг 18. Имена в текущей области
>>> dir() ['__builtins__', '__doc__', '__name__', 'keyword', 'sys']
Мы упоминали, что функция dir() является встроенной функцией, что означает, что нам не нужно импортировать модуль, чтобы использовать эту функцию.
Python распознает встроенные функции без нашего участия. И теперь мы видим это имя, __builtins__, возращенное обращением к dir(). Возможно, здесь имеется какая-то связь. Давайте введем имя __builtins__ в приглашение Python и посмотрим, скажет ли нам Python о нем что-нибудь интересное:
Листинг 19. Что такое __builtins__?
>>> __builtins__ <module '__builtin__' (built-in)>
Итак, __builtins__, похоже, является в текущей области именем, связанным с объектом модуля по имени __builtins__. (Поскольку модули - это не простые объекты с одиночными значениями, Python показывает информацию об этом модуле внутри угловых скобок.) Заметьте, что, если вы будете искать файл __builtin__.py на диске, то вы ничего не найдете. Этот особый объект модуля создается интерпретатором Python из ниоткуда, так как он содержит элементы, которые всегда доступны интерпретатору. И, несмотря на то, что физически файла не существует, мы все же можем применить нашу функцию dir() к этому объекту, чтобы увидеть все встроенные функции, объекты ошибок и несколько различных атрибутов, которые он содержит:
Листинг 20. Атрибуты модуля __builtins__
>>> dir(__builtins__) ['ArithmeticError', 'AssertionError', 'AttributeError', 'DeprecationWarning', 'EOFError', 'Ellipsis', 'EnvironmentError', 'Exception', 'False', 'FloatingPointError', 'IOError', 'ImportError', 'IndentationError', 'IndexError', 'KeyError', 'KeyboardInterrupt', 'LookupError', 'MemoryError', 'NameError', 'None', 'NotImplemented', 'NotImplementedError', 'OSError', 'OverflowError', 'OverflowWarning', 'ReferenceError', 'RuntimeError', 'RuntimeWarning', 'StandardError', 'StopIteration', 'SyntaxError', 'SyntaxWarning', 'SystemError', 'SystemExit', 'TabError', 'True', 'TypeError', 'UnboundLocalError', 'UnicodeError', 'UserWarning', 'ValueError', 'Warning', 'ZeroDivisionError', '_', '__debug__', '__doc__', '__import__', '__name__', 'abs', 'apply', 'bool', 'buffer', 'callable', 'chr', 'classmethod', 'cmp', 'coerce', 'compile', 'complex', 'copyright', 'credits', 'delattr', 'dict', 'dir', 'divmod', 'eval', 'execfile', 'exit', 'file', 'filter', 'float', 'getattr', 'globals', 'hasattr', 'hash', 'help', 'hex', 'id', 'input', 'int', 'intern', 'isinstance', 'issubclass', 'iter', 'len', 'license', 'list', 'locals', 'long', 'map', 'max', 'min', 'object', 'oct', 'open', 'ord', 'pow', 'property', 'quit', 'range', 'raw_input', 'reduce', 'reload', 'repr', 'round', 'setattr', 'slice', 'staticmethod', 'str', 'super', 'tuple', 'type', 'unichr', 'unicode', 'vars', 'xrange', 'zip']
Функция dir() работает со всеми типами объектов, включая строки, целые числа, списки, кортежи, словари, функции, экземпляры и методы классов и классы, определенные пользователем. Давайте применим dir() к строковому объекту и посмотрим, что возвратит Python. Как вы можете видеть, даже простая строка Python имеет ряд атрибутов:
Листинг 21. Атрибуты строки
>>> dir('this is a string') ['__add__', '__class__', '__contains__', '__delattr__', '__doc__', '__eq__', '__ge__', '__getattribute__', '__getitem__', '__getslice__', '__gt__', '__hash__', '__init__', '__le__', '__len__', '__lt__', '__mul__', '__ne__', '__new__', '__reduce__', '__repr__', '__rmul__', '__setattr__', '__str__', 'capitalize', 'center', 'count', 'decode', 'encode', 'endswith', 'expandtabs', 'find', 'index', 'isalnum', 'isalpha', 'isdigit', 'islower', 'isspace', 'istitle', 'isupper', 'join', 'ljust', 'lower', 'lstrip', 'replace', 'rfind', 'rindex', 'rjust', 'rstrip', 'split', 'splitlines', 'startswith', 'strip', 'swapcase', 'title', 'translate', 'upper', 'zfill']
Поэкспериментируйте со следующими примерами, чтобы выяснить, что они возвратят. Заметьте, что символ # обозначает начало комментария. Все от начала комментария и до конца строки игнорируется Python:
Листинг 22. Применяем dir() к другим объектам
dir(42) # Integer (and the meaning of life) dir([]) # List (an empty list, actually) dir(()) # Tuple (also empty) dir({}) # Dictionary (ditto) dir(dir) # Function (functions are also objects)
Чтобы продемонстрировать динамическую сущность интроспективных возможностей Python, давайте рассмотрим некоторые примеры, применяя dir() к классу, определенному пользователем, и нескольким экземплярам класса. Определим наш собственный класс интерактивно, создадим несколько экземпляров этого класса, и, добавив уникальный атрибут только к одному из этих экземпляров, посмотрим, сможет ли Python со всем этим разобраться. Ниже приведены результаты:
Листинг 23. Применяем dir() к классам, определенным пользователем, экземплярам класса и атрибутам
>>> class Person(object): ... """Person class.""" ... def __init__(self, name, age): ... self.name = name ... self.age = age ... def intro(self): ... """Return an introduction.""" ... return "Hello, my name is %s and I'm %s." % (self.name, self.age) ... >>> bob = Person("Robert", 35) # Создать экземпляр Person >>> joe = Person("Joseph", 17) # Создать еще один экземпляр >>> joe.sport = "football" # Присвоить одному экземпляру новый атрибут >>> dir(Person) # Атрибуты класса Person ['__class__', '__delattr__', '__dict__', '__doc__', '__getattribute__', '__hash__', '__init__', '__module__', '__new__', '__reduce__', '__repr__', '__setattr__', '__str__', '__weakref__', 'intro'] >>> dir(bob) # Атрибуты bob ['__class__', '__delattr__', '__dict__', '__doc__', '__getattribute__', '__hash__', '__init__', '__module__', '__new__', '__reduce__', '__repr__', '__setattr__', '__str__', '__weakref__', 'age', 'intro', 'name'] >>> dir(joe) # Заметьте, что joe имеет дополнительный атрибут ['__class__', '__delattr__', '__dict__', '__doc__', '__getattribute__', '__hash__', '__init__', '__module__', '__new__', '__reduce__', '__repr__', '__setattr__', '__str__', '__weakref__', 'age', 'intro', 'name', 'sport'] >>> bob.intro() # Вызов метода intro экземпляра bob "Hello, my name is Robert and I'm 35." >>> dir(bob.intro) # Атрибуты метода intro ['__call__', '__class__', '__cmp__', '__delattr__', '__doc__', '__get__', '__getattribute__', '__hash__', '__init__', '__new__', '__reduce__', '__repr__', '__setattr__', '__str__', 'im_class', 'im_func', 'im_self']
Функция type() помогает нам определить
Как было указано выше, каждый объект имеет тождественность, тип и значение. Важно заметить, что на один и тот же объект может указывать более одной переменной; с другой стороны, переменные могут ссылаться на объекты, которые выглядят похожими (у них одинаковый тип и значение), но нетождественны. Понятие тождественности объекта приобретает особо важное значение при внесении изменений в объект, таких как добавление элемента в список, что показано в приведенном ниже примере, в котором переменные blist и clist указывают на один и тот же объект списка. Как вы можете видеть, функция id() возвращает уникальный идентификатор для любого заданного объекта:
Листинг 30. "Рубеж"
>>> print id.__doc__ id(object) -> integer
Return the identity of an object. This is guaranteed to be unique among simultaneously existing objects. (Hint: it's the object's memory address.)
>>> alist = [1, 2, 3] >>> blist = [1, 2, 3] >>> clist = blist >>> clist [1, 2, 3] >>> blist [1, 2, 3] >>> alist [1, 2, 3] >>> id(alist) 145381412 >>> id(blist) 140406428 >>> id(clist) 140406428 >>> alist is blist # Возвращает 1 если True, 0 если False 0 >>> blist is clist # Аналогично 1 >>> clist.append(4) # Добавить элемент в конец списка >>> clist [1, 2, 3, 4] >>> blist # То же самое, поскольку они обе указывают на один и тот же объект [1, 2, 3, 4] >>> alist # А этот исходно только выглядел таким же [1, 2, 3] [1, 2, 3]
Функциональное программирование на языке Python
Автор: David Mertz, Ph.D., Applied Metaphysician, Gnosis Software, Inc.
Перевод: Яков Маркович, ведущий инженер-исследователь ""
Хотя пользователи обычно думают о Python как о процедурном и объектно-ориентированном языке, он содержит все необходимое для поддержки полностью функционального подхода к программированию.
В этой статье рассматриваются общие концепции функционального программирования и иллюстрируются способы реализации функционального подхода на Python.
Функциональное программирование на Python стало отложенным
В Python 2.2 были введены простые генераторы, а стандартные циклы перепродуманы в терминах итераторов. В Python 2.3 генераторы становятся стандартными (нет необходимости в _future_), а новый модуль itertools добавлен для гибкой работы с итераторами. Модуль itertools, по существу, это набор комбинаторных функций высшего порядка, которые, однако, работают с итераторами с отложенным вычислением, а не с конечными списками. В этой статье Дэвид рассматривает этот новый модуль, показывая выразительную силу, появившуюся с комбинаторными итераторами.
Функциональные циклы в Python
Замена циклов на выражения так же проста, как и замена условных блоков. 'for' может быть впрямую переведено в map(). Так же, как и с условным выполнением, нам понадобится упростить блок утверждений до одного вызова функции (мы близки к тому, чтобы научиться делать это в общем случае): #---------- Функциональный цикл 'for' в Python ----------#
for e in lst: func(e) # цикл на утверждении 'for'
map(func,lst) # цикл, основанный на map()
Кстати, похожая техника применяется для реализации последовательного выполнения программы, используя функциональный подход. Т.е., императивное программирование по большей части состоит из утверждений, требующих "сделать это, затем сделать то, затем сделать что-то еще". 'map()' позволяет это выразить так:
#----- Функциональное последовательное выполнение в Python ----------#
# создадим вспомогательную функцию вызова функции
do_it = lambda f: f()
# Пусть f1, f2, f3 (etc) - функции, выполняющие полезные действия
map(do_it, [f1,f2,f3]) # последовательное выполнение, реализованное на map()
В общем случае, вся главная программа может быть вызовом 'map()' со списком функций, которые надо последовательно вызвать, чтобы выполнить программу. Еще одно удобное свойство функций как объектов - то, что вы можете поместить их в список.
Перевести 'while' впрямую немного сложнее, но вполне получается :
#-------- Функциональный цикл 'while' в Python ----------#
# Обычный (основаный на утверждении 'while') цикл
while <cond>:
<pre-suite>
if <break_condition>:
break
else:
<suite>
# Рекурсивный цикл в функциональном стиле
def while_block():
<pre-suite>
if <break_condition>:
return 1
else:
<suite>
return 0
while_FP = lambda: (<cond> and while_block()) or while_FP()
while_FP()
Наш вариант 'while' все еще требует функцию while_block(), которая сама по себе может содержать не только выражения, но и утверждения (statements).
Но мы могли бы продолжить дальнейшее исключение утверждений в этой функции (как, например, замену блока 'if/else' в вышеописанном шаблоне на короткозамкнутое выражение). К тому же, обычная проверка на месте <cond> (наподобие 'while myvar==7') вряд ли окажется полезной, поскольку тело цикла (в представленном виде) не может изменить какие-либо переменные (хотя глобальные переменные могут быть изменены в while_block()). Один из способов применить более полезное условие - заставить while_block() возвращать более осмысленное значение и сравнивать его с условием завершения. Стоит взглянуть на реальный пример исключения утверждений:
#---------- Функциональный цикл 'echo' в Python ------------#
# Императивная версия "echo()"
def echo_IMP():
while 1:
x = raw_input("IMP -- ")
if x == 'quit':
break
else
print x
echo_IMP()
# Служебная функция, реализующая "тождество с побочным эффектом"
def monadic_print(x):
print x
return x
# FP версия "echo()"
echo_FP = lambda: monadic_print(raw_input("FP -- "))=='quit' or echo_FP()
echo_FP()
Мы достигли того, что выразили небольшую программу, включающую ввод/вывод, циклы и условия в виде чистого выражения с рекурсией (фактически - в виде функционального объекта, который при необходимости может быть передан куда угодно). Мы все еще используем служебную функцию monadic_print(), но эта функция совершенно общая и может использоваться в любых функциональных выражениях , которые мы создадим позже (это однократные затраты). Заметьте, что любое выражение, содержащее monadic_print(x) вычисляется так же, как если бы оно содержало просто x.В FP (в частности, в Haskell) есть понятие "монады" для функции, которая "не делает ничего, и вызывает побочный эффект при выполнении".
Функциональные инструменты программирования.
Следующие встроенные функции могут использоваться для многих операций со списками:
filter() map() reduce()
Функция filter() может использоваться для выборки значений из списка, условием служит функция пользователя. Синтаксис функции filter(имя_функции, список). Функция возвращает только те элементы списка, для которых значение функции принимает ненулевое(истинное) значение:
>>> def f(x): return x % 2 != 0 and x % 3 != 0#Выбор некоторых простых чисел ... >>> filter(f, range(2, 25)) [5, 7, 11, 13, 17, 19, 23]
Функция map() имеет следующий синтаксис: map(имя_функции, список). Map() возвращает список, являющийся результатом работы функции для каждого элемента списка:
>>> def cube(x): return x*x*x#Куб числа ... >>> map(cube, range(1, 11)) [1, 8, 27, 64, 125, 216, 343, 512, 729, 1000]
В функцию map можт быть передано несколько списков, необходимо только соблюдать соответствующее количество параметров в пользовательской функции, например:
>>> seq = range(8) >>> def square(x): return x*x#Квадрат числа ... >>> map(None, seq, map(square, seq)) [(0, 0), (1, 1), (2, 4), (3, 9), (4, 16), (5, 25), (6, 36), (7, 49)]
Функция reduce() имеет следующий синтаксис: reduce(имя_функции, список). Она возвращает единственное значение, являющееся выполнением пользовательской функции над первыми двумя элементами списка, затем с результатом выполнения функции и следующим элементом списка, например прогармма считает сумму всех чисел от 1 до 10:
>>> def add(x,y): return x+y ... >>> reduce(add, range(1, 11)) 55
Если в списке только один элемент, возвращается его значение, если список пустой, возникает исключительная ситуация. Если вы хотите, чтобы в случае пустого списка возвращалось какое-либо значение по умолчанию, то передайте его в качестве третьего аргумента функции reduce.
Функциональные возможности, присущие Python
Python поддерживает большую часть характеристик функционального программирования, начиная с версии Python 1.0. Но, как большинство возможностей Python, они присутствуют в очень смешанном языке. Так же как и с объектно-ориентированными возможностями Python, вы можете использовать то, что вам нужно, и игнорировать все остальное (пока оно вам не понадобится). В Python 2.0 было добавлено очень удачное "синтаксическое украшение" - списочные встраивания (list comprehensions). Хотя и не добавляя принципиально новых возможностей, списочные встраивания делают использование многих старых возможностей значительно приятнее.
Базовые элементы FP в Python - функции map(), reduce(), filter() и оператор lambda. В Python 1.x введена также функция apply(), удобная для прямого применения функции к списку, возвращаемому другой. Python 2.0 предоставляет для этого улучшенный синтаксис. Несколько неожиданно, но этих функций и всего нескольких базовых операторов почти достаточно для написания любой программы на Python; в частности, все управляющие утверждения ('if', 'elif', 'else', 'assert', 'try', 'except', 'finally', 'for', 'break', 'continue', 'while', 'def') можно представить в функциональном стиле, используя исключительно функции и операторы. Несмотря на то, что задача реального удаления всех команд управления потоком, возможно, полезна только для представления на конкурс "невразумительный Python" (с кодом, выглядящим как программа на Lisp'е), стоит уяснить, как FP выражает управляющие структуры через вызовы функций и рекурсию.
Хвостовая рекурсия
В этой статье мы еще немного вгрызлись в гранит функционального программирования. То, что осталось, меньше (и, возможно, проще) того, что сделано (название этого раздела - небольшая шутка; к сожалению, ее суть пока не разъяснена). Отличный способ дальнейшего освоения множества концепций ФП - просмотреть исходники модуля functional. Модуль прекрасно откомментирован и содержит примеры для большинства функций/классов. В этой статье не были рассмотрены некоторые мета-функции, упрощающие комбинацию и взаимодействие других функций. Они определенно стоят изучения для программиста на Python, стремящегося продолжить изучение подходов функционального программирования.
И опять о функциональном программировании на Python
David Mertz, Ph.D., Applied Metaphysician, Gnosis Software, Inc
Перевод:
Предыдущие статьи коснулись основных понятий функционального программирования (ФП). Эта статья продолжит обсуждение, иллюстрируя дополнительные возможности, главным образом реализованные в библиотеке Xoltar Toolkit: частичное вычисление функций (Currying,
карринг), функции высшего порядка (higher-order functions) и другие концепции.
Имя
Не все объекты имеют имя, но у тех, у которых оно есть, имя хранится в их атрибуте __name__. Заметьте, что имя выводится из объекта, а не из переменной, которая указывает на этот объект. Следующий пример подчеркивает это различие:
Листинг 27. Что скрыто в имени
$ python Python 2.2.2 (#1, Oct 28 2002, 17:22:19) [GCC 3.2 (Mandrake Linux 9.0 3.2-1mdk)] on linux2 Type "help", "copyright", "credits" or "license" for more information. >>> dir() # Функция dir() ['__builtins__', '__doc__', '__name__'] >>> directory = dir # Создать новую переменную >>> directory() # Работает просто как первоначальный объект ['__builtins__', '__doc__', '__name__', 'directory'] >>> dir.__name__ # Как тебя зовут? 'dir' >>> directory.__name__ # У меня такое же имя 'dir' >>> __name__ # А теперь о чем-нибудь совершенно другом '__main__'
Модули имеют имена, а сам интерпретатор Python считается модулем верхнего уровня, или основным модулем. Когда вы запускаете Python интерактивно, локальной переменной __name__ присваивается значение '__main__'. Подобным образом, когда вы выполняете модуль Python из командной строки, а не импортируете его в другой модуль, его атрибуту __name__ присваивается значение '__main__', а не действительное имя этого модуля. Так модули могут взглянуть на свое значение __name__, чтобы определиться, используются ли они в качестве поддержки для другой программы или как основное приложение, выполняемое из командной строки. Следующая идиома весьма распространена в модулях Python:
Листинг 28. Определяем: выполнение или импорт
if __name__ == '__main__': # Сделать здесь что-нибудь уместное, наподобие вызова # функции main(), определенной где-то в этом модуле. main() else: # Ничего не делать. Этот модуль был импортирован другим # модулем, который хочет воспользоваться этой функцией, # классом или другими полезными битами, которые он определил.
Информация о поиске модулей.
Когда вы импортируете модуль, то Питон ищет файл с таким именем не где-нибудь, а в определённых каталогах. Эти каталоги определены в переменной окружения PYTHONPATH вашей операционной системы. Эта переменная имеет структуру, схожую с переменной PATH и так же содержит в себе каталоги, где Питон будет искать модули. При отсутствии этой переменной, Питон будет искать модули в папке, куда были установлены его исполняемые файлы, а так как этот каталог зависит от инсталляции и частенько никаких модулей в нём нет, то удалять или изменять без особой необходимости PYTHONPATH не следует. Доступ к списку каталогов поиска можно получить также из списка sys.path модуля sys(import sys). Этот список можно изменять программно, путём стандартных операций со списками. Ни в коем случае не называйте свои модули так же, как названы стандартные модули Питона, так как это повлечёт за собой труднообнаружимую ошибку. Если подлежащий импорту скрипт находится в том же каталоге, что и вызывающая его программа, то нет необходимости обращаться к sys.path, так как Питон ищет модули также и в текущей директории.
Интерактивная справочная утилита Python
Как предлагалось выше, давайте напечатаем help и посмотрим, получим ли мы какую-нибудь информацию о ключевых словах:
Листинг 2. Запрашиваем у Python справочную информацию
>>> help Type help() for interactive help, or help(object) for help about object.
Поскольку мы не знаем, какой объект может содержать ключевые слова, давайте попробуем ввести help(), не указывая какой-то особый объект:
Листинг 3. Запускаем справочную утилиту
>>> help()
Welcome to Python 2.2! This is the online help utility.
If this is your first time using Python, you should definitely check out the tutorial on the Internet at http://www.python.org/doc/tut/.
Enter the name of any module, keyword, or topic to get help on writing Python programs and using Python modules. To quit this help utility and return to the interpreter, just type "quit".
To get a list of available modules, keywords, or topics, type "modules", "keywords", or "topics". Each module also comes with a one-line summary of what it does; to list the modules whose summaries contain a given word such as "spam", type "modules spam".
help>
Похоже, что мы немного продвинулись. Давайте введем keywords в приглашении справки:
Листинг 4. Запрашиваем справку о keywords
help> keywords
Here is a list of the Python keywords. Enter any keyword to get more help.
and elif global or assert else if pass break except import print class exec in raise continue finally is return def for lambda try del from not while
help> quit
You are now leaving help and returning to the Python interpreter. If you want to ask for help on a particular object directly from the interpreter, you can type "help(object)". Executing "help('string')" has the same effect as typing a particular string at the help> prompt.
>>>
Когда мы напечатали help(), мы увидели приветствие и некоторые указания, а затем приглашение справки. В приглашении мы ввели keywords и получили список ключевых слов Python.
Получив ответ на свой вопрос, мы вышли из справочной утилиты, увидев короткое прощальное сообщение, и вернулись к приглашению Python.
Как видно из этого примера, интерактивная справочная утилита Python отображает информацию на различные темы или об отдельном объекте. Эта справочная утилита довольно удобна и действительно использует интроспективные возможности Python. Однако буквальное использование справки не показывает, как она получает информацию. А поскольку задача этой статьи - раскрытие всех интроспективных секретов Python, нам необходимо незамедлительно выйти за рамки справочной утилиты.
Прежде чем выйти из справки, давайте воспользуемся ею, чтобы получить список доступных модулей. Модули - это просто текстовые файлы, которые содержат код Python и имена которых заканчиваются на .py. Если мы напечатаем в приглашении Python help('modules') или введем modules в приглашении справки, мы получим длинный список доступных модулей, который похож на неполный список, приведенный ниже. Попытайтесь сами установить, какие модули доступны на вашей системе, и понять, почему считается, что Python поставляется "вместе с батарейками".
Листинг 5. Получаем неполный список доступных модулей
>>> help('modules')
Please wait a moment while I gather a list of all available modules...
BaseHTTPServer cgitb marshal sndhdr Bastion chunk math socket CDROM cmath md5 sre CGIHTTPServer cmd mhlib sre_compile Canvas code mimetools sre_constants <...> bisect macpath signal xreadlines cPickle macurl2path site xxsubtype cStringIO mailbox slgc (package) zipfile calendar mailcap smtpd cgi markupbase smtplib
Enter any module name to get more help. Or, type "modules spam" to search for modules whose descriptions contain the word "spam".
>>>
Исключение команд управления потоком
Первое, о чем стоит вспомнить в нашем упражнении - то, что Python "замыкает накоротко" вычисление логических выражений. Оказывается, это предоставляет эквивалент блока 'if'/'elif'/'else' в виде выражения. Итак: #------ "Короткозамкнутые" условные вызовы в Python -----#
# Обычные управляющие конструкции
if <cond1>: func1()
elif <cond2>: func2()
else: func3()
# Эквивалентное "накоротко замкнутое" выражение
(<cond1> and func1()) or (<cond2> and func2()) or (func3())
# Пример "накоротко замкнутого" выражения
>>> x = 3
>>> def pr(s): return s
>>> (x==1 and pr('one')) or (x==2 and pr('two')) or (pr('other'))
'other'
>>> x = 2
>>> (x==1 and pr('one')) or (x==2 and pr('two')) or (pr('other'))
'two'
Казалось бы, наша версия условных вызовов с помощью выражений - не более, чем салонный фокус; однако все становится гораздо интересней, если учесть, что оператор lambda может содержать только выражения! Раз, как мы только что показали, выражения могут содержать условные блоки, используя короткое замыкание, выражение lambda позволяет в общей форме представить условные возвращаемые значения. Базируясь на предыдущем примере:
#--------- Lambda с короткозамкнутыми условными выражениями в Python -------#
>>> pr = lambda s:s
>>> namenum = lambda x: (x==1 and pr("one")) \
... or (x==2 and pr("two")) \
... or (pr("other"))
>>> namenum(1)
'one'
>>> namenum(2)
'two'
>>> namenum(3)
'other'
Исключение побочных эффектов
После всей проделанной работы по избавлению от совершенно осмысленных конструкций и замене их на невразумительные вложенные выражения, возникает естественный вопрос - "Зачем?!". Перечитывая мои описания характеристик FP, мы можем видеть, что все они достигнуты в Python. Но важнейшая (и, скорее всего, в наибольшей степени реально используемая) характеристика - исключение побочных эффектов или, по крайней мере, ограничение их применения специальными областями наподобие монад. Огромный процент программных ошибок и главная проблема, требующая применения отладчиков, случается из-за того, что переменные получают неверные значения в процессе выполнения программы. Функциональное программирование обходит эту проблему, просто вовсе не присваивая значения переменным.
Взглянем на совершенно обычный участок императивного кода. Его цель - распечатать список пар чисел, чье произведение больше 25. Числа, составляющие пары, сами берутся из двух других списков. Все это весьма напоминает то, что программисты реально делают во многих участках своих программ. Императивный подход к этой задаче мог бы выглядеть так:
#--- Императивный код для "печати произведений" ----#
# Процедурный стиль - поиск больших произведений с помощью вложенных циклов
xs = (1,2,3,4)
ys = (10,15,3,22)
bigmuls = []
#...прочий код...
for x in xs:
for y in ys:
#...прочий код...
if x*y > 25:
bigmuls.append((x,y))
#...прочий код...
#...прочий код...
print bigmuls
Этот проект слишком мал для того, чтобы что-нибудь пошло не так. Но, возможно, он встроен в код, предназначенный для достижения множества других целей в то же самое время. Секции, комментированные как "#...прочий код..." - места, где побочные эффекты с наибольшей вероятностью могут привести к ошибкам.
В любой из этих точек переменные xs, ys, bigmuls, x, y могут приобрести неожиданные значения в гипотетическом коде. Далее, после завершения этого куска кода все переменные могут иметь значения, которые могут ожидаются, а могут и не ожидаться посдедующим кодом. Очевидно, что инкапсуляция в функциях/объектах и тщательное управление областью видимости могут использоваться, чтобы защититься от этого рода проблем. Вы также можете всегда удалять ('del') ваши переменные после использования. Но, на практике, указанный тип ошибок весьма обычен.
Функциональный подход к нашей задаче полностью исключает ошибки, связанные с побочными эффектами. Возможное решение могло бы быть таким:
#--- Функциональный код для поиска/печати больших произведений на Python ----#
bigmuls = lambda xs,ys: filter(lambda (x,y):x*y > 25, combine(xs,ys))
combine = lambda xs,ys: map(None, xs*len(ys), dupelms(ys,len(xs)))
dupelms = lambda lst,n: reduce(lambda s,t:s+t, map(lambda l,n=n: [l]*n, lst))
print bigmuls((1,2,3,4),(10,15,3,22))
Мы связываем в примере анонимные ('lambda') функции с именами, но это не необходимо. Вместо этого мы могли просто вложить определения. Мы использовали имена как ради большей читаемости, так и потому, что combine() - в любом случае отличная служебная функция (генерирует список всех возможных пар элементов из двух списков). В свою очередь, dupelms() в основном лишь вспомогательная часть combine(). Хотя этот функциональный пример более многословен, чем императивный, при повторном использовании служебных функций код в собственно bigmuls() окажется, вероятно, более лаконичным, чем в императивном варианте.
Реальное преимущество этого функционального примера в том, что в нем абсолютно ни одна переменная не меняет своего значения. Какое-либо неожиданное побочное влияние на последующий код (или со стороны предыдущего кода) просто невозможно. Конечно, само по себе отсутствие побочных эффектов не гарантирует безошибочность кода, но в любом случае это преимущество.Однако заметьте, что Python, в отличие от многих функциональных языков, не предотвращает повторное привязывание имен bigmuls, combine и dupelms. Если дальше в процессе выполнения программы combine() начнет значить что-нибудь другое - увы! Можно было бы разработать класс-одиночку (Singleton) для поддержки однократного связывания такого типа (напр. 's.bigmuls', etc.), но это выходит за рамки настоящей статьи.
Еще стоит отметить, что задача, которую мы только что решили, скроена в точности под новые возможности Python 2.0. Вместо вышеприведенных примеров - императивного или функционального - наилучшая (и функциональная) техника выглядит следующим образом:
#----- Код Python для "bigmuls" с использованием списочных встраиваний (list comprehensions) -----#
print [(x,y) for x in (1,2,3,4) for y in (10,15,3,22) if x*y > 25]
Исключения и классы.
Все исключения являются классами. Чтобы определить новый тип исключения, мы фактически создаём новый класс, наследующий базовому классу Exception:
>>> class MyError(Exception): ... def __init__(self, value): ... self.value = value ... def __str__(self): ... return `self.value` ... >>> try: ... raise MyError(2*2) ... except MyError, e: ... print 'My exception occurred, value:', e.value ...
My exception occurred, value: 4
>>> raise MyError, 'oops!'
Traceback (most recent call last):
File "<stdin>", line 1, in ?
__main__.MyError: 'oops!'
Обычно в классах-исключениях делают небольшие конструкторы __init__, которые инициализируют поля класса, чтобы к ним впоследствие можно было бы обратиться:
class Error(Exception): """Базовый класс для исключений в модуле.""" pass
class InputError(Error): """Ошибка ввода данных.
Поля: expression – выражение, где произошла ошибка message – объяснение ошибки """
def __init__(self, expression, message): self.expression = expression self.message = message
class TransitionError(Error): """Возникает при неверной операции
Поля: previous – состояние до начала плохой операции next – состояние после операции message – объяснение, почему такая операция недопустима """
def __init__(self, previous, next, message): self.previous = previous self.next = next self.message = message
|
Исключительные ситуации.
Возникают либо при ошибке работы пользователя, либо при ошибке в логике программы. Исключительных ситуаций существует много: деление на нуль, использование несуществующего модуля или переменной, многие стандартные функции также генерируют исключения в случае неверной работы. Приведём примеры таких ситуаций:
>>> 10 * (1/0) Traceback (most recent call last):
File "<stdin>", line 1, in ?
ZeroDivisionError: integer division or modulo #Деление на нуль
>>> 4 + spam*3 Traceback (most recent call last):
File "<stdin>", line 1, in ?
NameError: spam
#Нет такого имени переменной >>> '2' + 2 Traceback (most recent call last):
File "<stdin>", line 1, in ?
TypeError: illegal argument type for built-in operation#Смешение разных типов
Конечно, всё было бы отлично, если пользователя удовлетворяли подобные сообщения, я лично в этом сильно сомневаюсь. Кроме этого, исключения могут генерировать ся в любой момент программы, что может вызвать потерю данных. По всем этим причинам желательно предусматривать возможность перехватывать исключения. Для этого используется блок try, который исполняет операторы внутри блока, но если возбуждается исключение, то оператор try ищет обработчик исключения(прерывая исполнение блока кода внутри try), обозначаемый except имя_исключения . Если обработчик не найден, то он ищется в других блоках try, если он не найден, то возникает непредвиденное(unhandled) исключение, которое отображается, как в предыдущем примере. Блок try выполняется до конца при отсутствии исключительных ситуаций, блоки except при этом пропускаются:
>>> while 1: ... try: ... x = int(raw_input("Введите число ")) #Здесь может возникнуть исключение ... break #Если всё правильно, то выходим из бесконечного while
... except ValueError: #А вот здесь обрабатывается исключение неверного формата числа ... print ''Ой-ой. Неправильное число. Попробуйте снова..."
Оператор except может принимать несколько имён исключений, оформленных в скобках через запятую:
... except (RuntimeError, TypeError, NameError): ... pass
Обработчики исключений могут принимать некоторые параметры, причём их число и последовательность специфична для каждого типа исключения. Также есть возможность создавать обработчик исключений по умолчанию, который обрабатывает все исключения, для которых не был определён конкретный обработчик:
import string, sys
try: f = open('myfile.txt') s = f.readline() i = int(string.strip(s)) except IOError, (errno, strerror): print "I/O ошибка(%s): %s" % (errno, strerror) except ValueError: print "Не могу преобразовать это в целое." except: print "Неожиданная ошибка:", sys.exc_info()[0] #Имя последнего исключения raise #Возбуждение данного исключения ещё раз(см. далее)
В блоке try имеется дополнительный оператор else, который выполняется, при отсутствии исключений в блоке try:
for arg in sys.argv[1:]: try: f = open(arg, 'r') except IOError: print 'не могу открыть', arg else: print arg, 'имеет длину в ', len(f.readlines()), ' строк' f.close()
Обработчики исключений способны обрабатывать исключения не только непосредственно в блоке try, но и в функциях, вызываемых из этого блока, например:
>>> def this_fails(): ... x = 1/0 ... >>> try: ... this_fails() #Функция, вызывающая исключительную ситуацию ... except ZeroDivisionError, detail: ... print 'Возбуждено исключение:', detail ... Возбуждено исключение: integer division or modulo
Использование комбинаторных функций в модуле itertools
Автор: Дэвид Мертц (David Mertz), разработчик, Gnosis Software, Inc.
Перевод:
Использование лямбда функций.
Лямбда функции пришли в Питон из языка Лисп и могут показаться необычными программисту на Си. Лямбда функции – это небольшие функции, которые создают другие функции, на своей основе. Чтобы быть более понятным, приведу такой пример: lambda a, b: a+b – вычисляет сумму двух своих аргументов. На основе функции, возвращающей lambda можно построить другие функции, например:
>>> def make_incrementor(n): ... return lambda x: x + n#x – параметр, который передаётся в порождённую функцию f(x) ... >>> f = make_incrementor(42) >>> f(0) 42
>>> f(1) 43
Использование списков, как очередей.
Очередь – это другая структура данных, организованнная по принципу “Первым пришёл, первым ушёл”(FIFO). В Питоне нет встроенного класса очереди, но вы можете также использовать списки Питона: для добавления элемента используйте append, а для получения последнего – метод pop(0)(метод pop удаляет элемент). Например:
>>> queue = [1, 2, 3] >>> queue.append(4) # Terry arrives >>> queue.append(5) # Graham arrives >>> queue.pop(0) 5
>>> queue.pop(0) 4
>>> queue [1, 2, 3]
Использование списков, как стеков.
Стек – это структура данных, организованнная по принципу “Последним пришёл, первым ушёл”(LIFO). В Питоне нет встроенного класса стека, но вы можете использовать списки Питона так, как они были бы стеками: для добавления элемента используйте append, а для получения последнего – метод pop() без аргумента(метод pop удаляет элемент). Например:
>>> stack = [3, 4, 5] >>> stack.append(6) >>> stack.append(7) >>> stack [3, 4, 5, 6, 7]
>>> stack.pop() 7
>>> stack [3, 4, 5, 6]
>>> stack.pop() 6
>>> stack.pop() 5
>>>> stack [3, 4]
Экземпляры
Хотя функция type() и выдает тип объекта, с помощью функции isinstance() мы также можем выяснить, является ли объект экземпляром определенного типа или определенного пользователем класса:
Листинг 33. Ты один из них?
>>> print isinstance.__doc__ isinstance(object, class-or-type-or-tuple) -> Boolean
Return whether an object is an instance of a class or of a subclass thereof. With a type as second argument, return whether that is the object's type. The form using a tuple, isinstance(x, (A, B, ...)), is a shortcut for isinstance(x, A) or isinstance(x, B) or ... (etc.).
>>> isinstance(42, str) 0 >>> isinstance('a string', int) 0 >>> isinstance(42, int) 1 >>> isinstance('a string', str) 1
Как изучать объекты Python
Интроспекция предоставляет полезную информацию об объектах вашей программы. Python, динамический, объектно-ориентированный язык программирования, обеспечивает потрясающую поддержку интроспекции. В этой статье демонстрируются многие из его возможностей, начиная самыми простыми видами помощи и заканчивая более сложными формами изысканий.
Ключи.
В Питоне есть другая возможность передавать значения в функцию – через ключи в формате 'ключ=значение', например:
def parrot(voltage, state='крутой', action='шок', type='Волнистый'): print "-- "Попугай не будет", action, print "если вы пропустите ток в", voltage, "вольт через него." print "-- Попугай", type print "-- Это", state, "!"
Такую функцию можно вызвать любым из способов ниже:
parrot(1000) parrot(action = 'А-а-а-а-а-у-у-ш-ш-ш', voltage = 1000000) parrot('тысячу', state = 'заболеет') parrot('миллион', 'вернётся к жизни', 'прыжок')
А вот таким образом функцию вызывать нельзя:
parrot() # требуемый аргумент опущен parrot(voltage=5.0, 'dead') #аргумент не ключ использован как ключ parrot(110, voltage=220) # повторение значение одного и того же аргумента parrot(actor='John Cleese') # неизвестный ключ
В общем, список ключей может содержать ключи со значениями в любом порядке, причём неважно, имеет ли данный аргумент значение по умолчанию или нет. Но важно помнить, что нельзя дублировать аргумент, переданный в функцию, ключом. Например, вызов функции так, как показано ниже вызовет по этой причине ошибку:
>>> def function(a): ... pass ...
>>> function(0, a=0) ERROR: ключ переопределён
Если в заголовке функции присутствует параметр в формате **имя, то в него включаются все ключи, которые были переданы в функцию, но не были определены в её заголовке. С этим параметром может также употребляться другой параметр, имеющий формат *имя. В него передаются аргументы, не входящие в список обязательных параметров функции. Учтите, что аргумент *имя должен стоять перед **имя:
def cheeseshop(kind, *arguments, **keywords): print "-- У вас есть какие-нибудь", kind, '?' print "-- Нет, всех съели" for arg in arguments: print arg print '-'*40 for kw in keywords.keys(): print kw, ':', keywords[kw]
Функция может быть вызвана, например, так:
cheeseshop('Зонты', "Очень жаль.", "Действительно ужасно жаль, товарищ.", client='Михаил Горбачёв', shopkeeper='Борис Ельцин', sketch='Белый Дом')
и вот каким будет результат работы:
-- У вас есть какие-нибудь зонты?
-- Нет, всех съели
Очень жаль.
Действительно, ужасно жаль.
----------------------------------------
client : Михаил Горбачёв
shopkeeper : Борис Ельцин
sketch : Белый Дом
Комбинаторные функции
Несколько реальных комбинаторных функций в itertools уже были вскользь упомянуты. ifilter(), izip() и imap() ведут себя именно так, как следовало бы ожидать от соответствующих функций над последовательностями. ifilterfalse() - вспомогательная функция, так что вам не нужно инвертировать предикатную функцию в lambda и def (и это значительно экономит накладные расходы на вызов функции). Но функционально вы могли бы определить ifilterfalse() (приблизительно, игнорируя предикат None) как:
def ifilterfalse(predicate, iterable): return ifilter(lambda predicate: not predicate, iterable)
Функции dropwhile() и takewhile() разделяют итератор предикатом. Первая игнорирует возвращаемые элементы, пока предикат не выполнен, а вторая выдает, пока предикат выполняется. dropwhile пропускает неопределённое число первоначальных элементов итератора, чтобы он смог начать итерации только после задержки. takewhile() запускается немедленно, но завершает итератор, если передаваемый предикат становится true.
Функция islice(), по существу, просто итераторная версия среза списка. Вы можете задать начало, останов и шаг, как с регулярными срезами. Если начало задано, ряд элементов отбрасывается, пока передаваемый итератор не достигнет требуемого элемента. Это еще один случай, когда, как я думаю, возможно усовершенствовать Python - самое лучшее для итераторов было бы просто распознать разрезы, как делают списки (в качестве синонима того, что делает islice()).
Последняя функция starmap() - это легкая вариация imap(). Если функция, которая передается как аргумент, принимает набор аргументов, переданный итератор должен выдавать кортежи надлежащего размера. По существу, это то же самое, что и imap() с несколькими передаваемыми итерируемыми аргументами, только с набором итерируемых аргументов, предварительно комбинированных izip().
Компиляция скриптов на Питоне.
Питон является интерпретируемым языком, то есть интерпретатор считывает из текста некие операторы и сразу же их выполняет, при этом осуществляется контроль за синтаксисом языка. Есть способ ускорить загрузку скриптов на Питоне, это особенно важно для часто используемых модулей. Для этого существуют компилированные модули(своеобразный аналог байт-кода языка Java). Если в каталоге существуют два файла с одним именем файла: один с расширением .py, а другой - .pyc(компилированный), то компилированный файл будет использоваться только в том случае, когда источником для его компиляции являлся файл .py(одинаковая версия модуля). При исполнении какого-либо модуля, файл .pyc генерируется автоматически, так что об этом вам не приходится заботиться.
Константные списки.
Мы до сих пор рассматривали списки, т.е. последовательности, элементы которых могут быть доступны для изменения по отдельности. Другим типом последовательности является константный список(tuple). Такой список в теле программы обозначается списком элементов через запятую, может содержать в себе элементы различных типов, но изменить их через индекс не удастся(см. строки). Константные списки могут содержать в себе в качестве элементов другие последовательности. Для списков константного типа определены операции присваивания, склеивания +, индексации(только чтение). Использовать такие списки удобно при доступе к базам данных(одинаковые поля) и системам координат. Рассмотрим примеры константных списков:
>>> t = 12345, 54321, 'привет!' >>> t[0] 12345
>>> t (12345, 54321, 'привет!')
>>> #Вложенный список: ... u = t, (1, 2, 3, 4, 5) >>> u ((12345, 54321, 'привет!'), (1, 2, 3, 4, 5))#Выходные данные тоже в скобках
Внимание: для создания пустого константного списка просто присвойте ему значение (), для создания списка, состоящего из одного элемента сделайте следующее: “имя_списка = единственный_элемент,” - не забудьте запятую в конце(выглядит не слишком приятно):
>>> empty = () >>> singleton = 'привет', # <-- не забыть бы про запятую >>> len(empty) 0
>>> len(singleton) 1
>>> singleton ('привет',)
Создание константного списка из других переменных, вроде “список = 'привет', 'пока', a” является примером упаковки переменных в константный список. Можно провести обратное действие – распаковку, если такой список присваивается нескольким переменным(их число должно быть равно числу элементов в константном списке):
>>> x, y, z = t
Внимание: несколько элементов всегда упаковываются в константный список, в то время как распакована вышеописанным образом может быть абсолютно любая последовательность
Краткий обзор объектно-ориентированного программирования (ООП)
Давайте, потратив полминуты, вспомним, что такое ООП. В языке объектно-ориентированного программирования можно определять классы, задача которых - объединить связанные данные и поведение. Эти классы могут наследовать некоторые или все свойства своих родителей, они также определяют свои собственные атрибуты (данные) или методы (поведение). В результате, классы, как правило, выступают в качестве шаблонов для создания экземпляров (которые время от времени называют просто объектами). Различные экземпляры одного и того же класса обычно имеют разные данные, хотя они будут представлены в одинаковом виде, например, у обоих объектов класса 'Employee' bob и jane есть .salary и .room_number, но значения room (комната) и salary (жалование) у каждого различны.
Некоторые объектно-ориентированные языки программирования, включая Python, предусматривают интроспективные (или рефлексивные) объекты. Другими словами, интроспективный объект может сам себя описывать: к какому классу принадлежит этот экземпляр? Кто предки этого класса? Какие методы и атрибуты доступны объекту? Интроспекция позволяет функции или методу, управляющему объектами, принимать решения, основываясь на том, какой вид объекта передается. Даже без интроспекции функции часто ветвятся, опираясь на данные экземпляра - например, маршрут к jane.room_number отличается от пути к bob.room_number, поскольку они в "различных комнатах" (значения room у них различны). С помощью интроспекции также можно безошибочно вычислить bonus (премиальные) jane, пропустив это вычисление для bob, например, потому что у jane есть атрибут .profit_share или из-за того, что bob является экземпляром производного класса Hourly(Employee).
Метаклассы: решение, требующее проблемы?
"Метаклассы - большая магия, чем нужно 99% пользователей. Если вы задаетесь вопросом, нужны ли они вам, значит, они вам не нужны (те, кому они действительно нужны, точно это знают, и им не требуется объяснение, зачем)". Тим Питерс (Tim Peters), крупнейший авторитет в области Python
Методы (классов), как и обычные функции, могут возвращать объекты. В этом смысле очевидно, что фабрики классов столь же могут быть классами, как и функциями. В частности, Python 2.2+ предоставляет специальный класс, называемый type, который именно и есть такая фабрика классов. Разумеется, читатели узнают в type() менее претенциозную встроенную функцию более ранних версий Python - к счастью, поведение старой функции type() поддерживается классом type (другими словами, type(obj) возвращает тип/класс объекта obj). Новый класс работает в качестве фабрики классов точно так же, как прежде делала функция new.classobj:
Листинг 3. type в качестве метакласса фабрики классов
>>> X = type('X',(),{'foo':lambda self:'foo'}) >>> X, X().foo() (<class '__main__.X'>, 'foo')
Но поскольку теперь type - это (мета)класс, вы можете создать от него производный класс:
Листинг 4. Потомок type как фабрика классов
>>> class ChattyType(type): ... def __new__(cls, name, bases, dct): ... print "Allocating memory for class", name ... return type.__new__(cls, name, bases, dct) ... def __init__(cls, name, bases, dct): ... print "Init'ing (configuring) class", name ... super(ChattyType, cls).__init__(name, bases, dct) ... >>> X = ChattyType('X',(),{'foo':lambda self:'foo'}) Allocating memory for class X Init'ing (configuring) class X >>> X, X().foo() (<class '__main__.X'>, 'foo')
Магические методы .__new__() и .__init__() являются специальными, но концептуально они такие же, как и у любого другого класса. Метод .__init__() позволяет конфигурировать созданный объект; метод .__new__() разрешает настраивать его создание. Последний, разумеется, не используется широко, но существует для каждого класса нового стиля Python 2.2 (обычно наследуется, а не подменяется).
У потомков type есть одно свойство, которое необходимо учитывать; на нем ловятся все, кто впервые использует метаклассы. Первый аргумент в методах обычно называется cls, а не self, поскольку эти методы обрабатывают созданный класс, а не метакласс. На самом деле, в этом нет ничего особенного; все методы связываются со своимиэкземплярами, а экземпляр метакласса является классом. Неспециальное имя делает это более очевидным:
Листинг 5. Прикрепление методов класса к созданным классам
>>> class Printable(type): ... def whoami(cls): print "I am a", cls.__name__ ... >>> Foo = Printable('Foo',(),{}) >>> Foo.whoami() I am a Foo >>> Printable.whoami() Traceback (most recent call last): TypeError: unbound method whoami() [...]
Вся эта удивительно непримечательная технология сопровождается некими синтаксическими украшениями, упрощающими работу с метаклассами и одновременно запутывающими новых пользователей. В этом дополнительном синтаксисе есть несколько элементов. Порядок интерпретации этих новых вариаций мудреный. Классы могут наследовать метаклассы от своих предков - заметьте, что это не одно и то же, что наличие метаклассов в качестве предков (еще одно обычное заблуждение). Для классов старого стиля определение глобальной переменной __metaclass__ приводит к использованию метаклассса, определенного пользователем. Однако по большей части самый безопасный подход - задать атрибут класса __metaclass__ для класса, который хочет быть созданным по метаклассу, определенному пользователем. Вы должны задать эту переменную в самом определении класса, поскольку метакласс не используется, если этот атрибут задан позднее (после того как объект класса уже был создан). Например:
Листинг 6. Задание метакласса с атрибутом класса
>>> class Bar: ... __metaclass__ = Printable ... def foomethod(self): print 'foo' ... >>> Bar.whoami() I am a Bar >>> Bar().foomethod() foo
Метапреимущества
Пакет gnosis.magic содержит несколько утилит для работы с метаклассами, а также некоторые примеры метаклассов, которые можно применять в аспектно-ориентированном программировании. Наиболее важная из этих утилит - import_with_metaclass(). Эта функция, задействованная в предыдущем примере, позволяет импортировать произвольный модуль, создавая все классы этого модуля с использованием метакласса, определенного пользователем, а не type. Какую бы новую возможность вы ни захотели задать в этом модуле, она может быть определена в метаклассе, который вы создаете (или получаете). gnosis.magic содержит некоторые подключаемые метаклассы сериализации; другой пакет мог бы включать возможности трассировки, объектную персистентность, регистрацию исключений или же что-нибудь еще.
Функция import_with_metaclass() иллюстрирует некоторые возможности программирования метаклассов:
Листинг 13. Функция import_with_metaclass() из [gnosis.magic]
def import_with_metaclass(modname, metaklass): "Module importer substituting custom metaclass" class Meta(object): __metaclass__ = metaklass dct = {'__module__':modname} mod = __import__(modname) for key, val in mod.__dict__.items(): if inspect.isclass(val): setattr(mod, key, type(key,(val,Meta),dct)) return mod
В этой функции стоит обратить внимание на стиль - обыкновенный класс Meta создан с использованием заданного метакласса. Но как только Meta добавлен в качестве предка, его потомки также создаются с помощью этого метакласса. В принципе, класс, подобный Meta, мог бы предоставлять и генератор метакласса, и ряд наследуемых методов - эти два аспекта наследования являются ортогональными.
"Метапрограммный" ответ
Базовая система ООП, очерченная выше, является достаточно мощной. Однако, в этом описании один момент не получил должного внимания: в Python (и других языках) сами классы являются объектами, которые можно передавать и подвергать интроспекции. Но поскольку объекты, как отмечалось, создаются с использованием классов в качестве шаблонов, то что же является шаблоном для создания классов? Разумеется, метаклассы.
В Python всегда были метаклассы. Однако, технология, задействованная в метаклассах, стала гораздо более очевидной с выходом Python 2.2. А именно, в версии 2.2 Python перестал быть языком только с одним специальным (обычно невидимым) метаклассом, который создавал каждый объект класса. Теперь программисты могут наследоваться от встроенного метакласса type и даже динамически генерировать классы с различными метаклассами. Разумеется, только то, что вы можете манипулировать метаклассами на Python 2.2, еще не объясняет, зачем вам это.
Более того, вам не нужно использовать метаклассы, определенные пользователем, чтобы управлять созданием классов. Несколько менее головоломная концепция - фабрика классов (class factory): обыкновенная функция может возвращать класс, который был динамически создан в пределах тела функции. В традиционном синтаксисе Python вы можете написать:
Листинг 1. Традиционная фабрика классов на Python 1.5.2
Python 1.5.2 (#0, Jun 27 1999, 11:23:01) [...] Copyright 1991-1995 Stichting Mathematisch Centrum, Amsterdam >>> def class_with_method(func): ... class klass: pass ... setattr(klass, func.__name__, func) ... return klass ... >>> def say_foo(self): print 'foo' ... >>> Foo = class_with_method(say_foo) >>> foo = Foo() >>> foo.say_foo() foo
Функция фабрики class_with_method() динамически создает и возвращает класс, содержащий метод/функцию, передаваемую в эту фабрику. Сам класс обрабатывается в пределах тела функции до того, как он возвращен. Модуль new обеспечивает более лаконичное выражение, но без возможности определения пользователем дополнительного кода в пределах тела фабрики классов. Например:
Листинг 2. Фабрика классов в модуле new
>>> from new import classobj >>> Foo2 = classobj('Foo2',(Foo,),{'bar':lambda self:'bar'}) >>> Foo2().bar() 'bar' >>> Foo2().say_foo() foo
Во всех этих случаях поведение класса (Foo, Foo2) не записано непосредственно в виде кода, а создается посредством вызова во время исполнения функций с вычисляемыми аргументами. Следует подчеркнуть, что динамически создаются именно сами классы, а не просто экземпляры.
Модуль keyword
Давайте вернемся к нашему вопросу о ключевых словах Python. Даже несмотря на то, что справка предоставила нам список ключевых слов, оказывается, что часть информации справки жестко закодирована. Список ключевых слов, получается, жестко закодирован, что в конце концов не слишком интроспективно. Давайте посмотрим, сможем ли мы получить такую информацию непосредственно из одного из модулей в стандартной библиотеке Python. Если мы напечатаем help('modules keywords') в приглашении Python, то увидим следующее:
Листинг 14. Запрашиваем справку о модулях с ключевыми словами
>>> help('modules keywords')
Here is a list of matching modules. Enter any module name to get more help.
keyword - Keywords (from "graminit.c")
Похоже, что модуль keyword содержит ключевые слова. Открыв файл keyword.py в текстовом редакторе, можно увидеть, что Python действительно создает список ключевых слов, явно доступных в виде атрибута kwlist модуля keyword. В модуле keyword также приводятся комментарии о том, что этот модуль генерируется автоматически на основе исходного кода самого Python, гарантируя точность и полноту списка ключевых слов:
Листинг 15. Список ключевых слов модуля keyword
>>> import keyword >>> keyword.kwlist ['and', 'assert', 'break', 'class', 'continue', 'def', 'del', 'elif', 'else', 'except', 'exec', 'finally', 'for', 'from', 'global', 'if', 'import', 'in', 'is', 'lambda', 'not', 'or', 'pass', 'print', 'raise', 'return', 'try', 'while', 'yield']
Модуль pickle
Для чтения/записи в файл используются строки. То есть вам необходимо прежде чем записать что-либо в файл преобразовать это что-то в строку. С числовыми значениями всё легко: в модуле string есть множество функций для преобразования чисел в строки и строк в числа(например, строка в целое “string.atoi()”). Но как быть со сложными объектами: списками, например. Для этой цели в Питоне существует особый модуль: pickle, который может преобразовать в строку любой объект(даже некоторый формат кода самого Питона!) и записать его в файл. При этом при помощи модуля pickle можно выполнить обратную операцию из строки, генерированной pickle, в переменную(модуль сам распознает её тип). Причём, функционирование модуля pickle одинаково в любой реализации Питона, поэтому использование подобного механизма способствует переносимости кода и помогает повторно использовать какие-либо сложные объекты. Пример использования модуля pickle(полное описание я пока не перевёл):
pickle.dump(x, f)#Выгрузка содержимого x в файл f x = pickle.load(f)#Загрузка x из файла f.
Модуль sys
Один из модулей, предоставляющих внутреннюю информацию о самом Python, - это модуль sys. Вы используете модуль, импортируя его и ссылаясь на его содержимое (как, например, переменные, функции и классы) с помощью нотации точка (.). Модуль sys содержит множество переменных и функций, которые предоставляют интересную и подробную информацию о текущем интерпретаторе Python. Давайте рассмотрим некоторые из них. И снова мы собираемся, запустив Python интерактивно, вводить команды в приглашении Python. Первое, что мы сделаем - это импортируем модуль sys. Затем введем переменную sys.executable, которая содержит путь к интерпретатору Python:
Листинг 6. Импортируем модуль sys
$ python Python 2.2.2 (#1, Oct 28 2002, 17:22:19) [GCC 3.2 (Mandrake Linux 9.0 3.2-1mdk)] on linux2 Type "help", "copyright", "credits" or "license" for more information. >>> import sys >>> sys.executable '/usr/local/bin/python'
Если мы введем строку кода, который не содержит ничего кроме имени объекта, Python ответит, показывая представление этого объекта, которое - для простых объектов - как правило, есть значение этого объекта. В этом случае, поскольку выведенное значение заключено в кавычки, мы можем предположить, что sys.executable, вероятно, строковый объект. Позже мы изучим другие, более точные, способы определения типа объекта, однако просто ввод имени объекта в приглашении Python - это быстрый и легкий вид интроспекции.
Давайте рассмотрим некоторые другие полезные атрибуты модуля sys.
Переменная platform сообщает, в какой операционной системе мы работаем:
Атрибут sys.platform
>>> sys.platform 'linux2'
Текущая версия Python доступна и в виде строки, и в виде кортежа (кортеж содержит последовательность объектов):
Листинг 8. Атрибуты sys.version и sys.version_info
>>> sys.version '2.2.2 (#1, Oct 28 2002, 17:22:19) \n[GCC 3.2 (Mandrake Linux 9.0 3.2-1mdk)]' >>> sys.version_info (2, 2, 2, 'final', 0)
Переменная maxint показывает наибольшее допустимое целое значение:
Атрибут sys.maxint
>>> sys.maxint 2147483647
Переменная argv - это список, содержащий параметры командной строки, если она была задана. Первый элемент, argv[0], это путь к скрипту, который был запущен. Когда мы работаем с Python интерактивно, его значением является пустая строка:
Листинг 10. Атрибут sys.argv
>>> sys.argv ['']
Если мы запустим другую оболочку Python, как, например, PyCrust (за более подробной информацией о PyCrust см. ссылку, приведенную в ), то увидим что-нибудь вроде этого:
Листинг 11. Атрибут sys.argv при использовании PyCrust
>>> sys.argv[0] '/home/pobrien/Code/PyCrust/PyCrustApp.py'
Переменная path - это путь поиска модуля, список каталогов, в которых Python будет искать модули во время импорта. Пустая строка, ' ', в первой позиции относится к текущему каталогу:
Листинг 12. Атрибут path
>>> sys.path ['', '/home/pobrien/Code', '/usr/local/lib/python2.2', '/usr/local/lib/python2.2/plat-linux2', '/usr/local/lib/python2.2/lib-tk', '/usr/local/lib/python2.2/lib-dynload', '/usr/local/lib/python2.2/site-packages']
Переменная modules - это словарь, который отображает имена модулей в объекты модулей для всех загруженных в текущий момент модулей. Как можно видеть, Python загружает определенные модули по умолчанию:
Листинг 13. Атрибут sys.modules
>>> sys.modules {'stat': <module 'stat' from '/usr/local/lib/python2.2/stat.pyc'>, '__future__': <module '__future__' from '/usr/local/lib/python2.2/__future__.pyc'>, 'copy_reg': <module 'copy_reg' from '/usr/local/lib/python2.2/copy_reg.pyc'>, 'posixpath': <module 'posixpath' from '/usr/local/lib/python2.2/posixpath.pyc'>, 'UserDict': <module 'UserDict' from '/usr/local/lib/python2.2/UserDict.pyc'>, 'signal': <module 'signal' (built-in)>, 'site': <module 'site' from '/usr/local/lib/python2.2/site.pyc'>, '__builtin__': <module '__builtin__' (built-in)>, 'sys': <module 'sys' (built-in)>, 'posix': <module 'posix' (built-in)>, 'types': <module 'types' from '/usr/local/lib/python2.2/types.pyc'>, '__main__': <module '__main__' (built-in)>, 'exceptions': <module 'exceptions' (built-in)>, 'os': <module 'os' from '/usr/local/lib/python2.2/os.pyc'>, 'os.path': <module 'posixpath' from '/usr/local/lib/python2.2/posixpath.pyc'>}