Введение в язык Питон

         

Модули в Питоне.


Работая в интерактивном режиме интерпретатора, вы, наверное, решили, что писать большие программы в Питоне невозможно, так как после выхода из интерпретатора определения ваших функций “исчезают” бесследно. А хотелось бы иметь возможность написать несколько функций в одном файле и потом иметь возможность обращаться к ним из других программ(принцип повторного использования кода). Такая возможность в Питоне существует. Это технология модулей, то есть текстовых файлов, содержащих в себе какой-либо набор функций(желательно объединённых по какому-либо признаку). Большинство функций стандартной библиотеки Питона реализовано именно в модулях. Создать модуль очень просто: просто зайдите в свой любимый текстовый редактор, напишите определения своих функций с помощью ключевого слова def, не забывайте также об отступах, которые выделяют блоки в программе. Ставить символы >>> и .... в текстовом редакторе нельзя, так как это вызовет синтаксическую ошибку. Модули Питона имеют расширение .py и могут содержать только текст. Внутри модуля существует глобальная переменная __name__, в которой содержится имя текущего модуля. Напрмер, создайте файл с именем fibo.py, содержащим следующий код:

#Модуль Фибоначчи

def fib(n): # выводит числа Фибоначчи на экран a, b = 0, 1 while b < n: print b, a, b = b, a+b

def fib2(n): # возвращает числа Фибоначчи, не превосходящие n result = [] a, b = 0, 1 while b < n: result.append(b) a, b = b, a+b return result

Сохраните файл и зайдите в интерпретатор Питона. К функциям из модуля можно получить доступ посредством ключевого слова import имя_модуля:

>>> import fibo

Теперь вы можете вызывать любые функции импортированного модуля в форме “имя_модуля.имя_функции”, то есть обращаться к функциям и переменным модуля не непосредственно, а через имя содержащего их модуля:

>>> fibo.fib(1000) 1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987

>>> fibo.fib2(100) [1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89]

>>> fibo.__name__ 'fibo'

Существует возможность присваивать локальной переменной любые переменные и функции модуля и обращаться к ним через эту переменную(в памяти локальная переменная указывает на ту же область памяти, что и элемент модуля):

>>> fib = fibo.fib >>> fib(500) 1 1 2 3 5 8 13 21 34 55 89 144 233 377



Наследование.


Одиночное наследование.

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

Синтаксис класса, наследующего одному классу:

class имя_наследуемого_класса(имя_класса_родителя):

элемент_класса_1 . . . элемент_класса_n



При этом класс-родитель может находиться в другой области действия, например, в другом модуле, тогда имя класса-родителя отделяется от имени модуля точкой:

class имя_наследуемого_класса(имя_модуля.имя_класса_родителя): элемент_класса_1 . . . элемент_класса_n

Для обращения к элементам и методам родительского класса используется синтаксис: имя_родителя.имя_поля или имя_родителя.имя_метода(аргументы).

Множественное наследование.

Часто бывает нужным получить доступ к элементам многих классов сразу, тогда можно воспользоваться механизмом множественного наследования. С точки зрения программы, разницы между одиночным и множественным наследованием нет никакой, то есть одиночное наследование – это частный случай множественного наследования. Для обращения к элементам базовых классов используется синтаксис, подобный одиночному наследованию, только обращаться можно к элементам разных родительских классов. Синтаксис класса, наследующего многим:

class имя_наследуемого_класса(имя_класса_родителя1, имя_класса_родителя2, ... имя_класса_родителяN): элемент_класса_1 . . . элемент_класса_n

При этом классы-родители могут находиться в разных областях действия, тогда необходимо указать область действия каждого класса(см. выше).



Некоторые рекомендации


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



Объяснение новой концепции


Понятие итераторов было введено в Python версии 2.2. Хотя это не совсем верно; намеки на эту идею присутствовали уже в более ранней функции xrange() и в файловом методе .xreadlines(). Python 2.2 обобщил это понятие в большей части своих внутренних реализаций и значительно упростил программирование итераторов, определенных пользователем, введя ключевое слово yield (присутствие yield превращает функцию в генератор, который в свою очередь возвращает итератор).

Привлекательность итераторов объясняется двумя причинами. Работа с данными как последовательностями - часто наиболее простой подход, а последовательность, обрабатываемая в линейном порядке, на самом деле зачастую не должна существовать вся сразу.

Предупреждения x*() предоставляют очевидные примеры этих принципов. Если вы хотите сделать что-нибудь миллиард раз, ваша программа, вероятно, будет исполняться какое время, однако, в общем не нужно требовать для нее много памяти. Подобным образом для многих типов файлов обработка может выполняться построчно, и нет необходимости хранить в памяти весь файл. Все виды других последовательностей также можно наилучшим образом подвергнуть отложенному вычислению; они могли бы полагаться на данные, поступающие в виде приращений по каналу, или на вычисления, выполняемые шаг за шагом.

Большую часть времени итератор используется в цикле for точно так же, как и истинная последовательность. Итераторы предоставляют метод .next(), который может быть явно запущен, но в 99% времени то, что вы увидите - это что-нибудь вроде:

for x in iterator: do_something_with(x)

Этот цикл завершается, когда закулисное обращение к iterator.next() возбуждает исключение StopIteration. Между прочим, истинная последовательность может быть превращена в итератор вызовом iter(seq) - это нисколько не сбережет память, но может быть полезно в функциях, обсуждаемых ниже.



Области действия переменных.


В технологии классов важную роль играет область действия переменной. Вы можете обращаться к переменной только внутри блока, где она была определена(в первый раз использована). Если переменная была определена внутри функции, то вне функции к ней нет доступа, если функция определена в основном коде, то она становится глобальной для данной программы(файла-модуля). Но если переменная или функция определена внутри модуля, то обращаться к ней непосредственно по имени невозможно(см.модули). Для обращения к переменной, находящейся внутри модуля, вне модуля чаще всего используется синтаксис имя_модуля.имя_переменной. Для обращения к переменной внутри данной программы, можно воспользоваться модулем главной программы __main__, все переменные и методы, объявленные в различных модулях создаются и разрушаются в определённом порядке. Например, когда вы импортируете модуль, то создаются все объекты, объявленные в нём. Для встроенных функций интерпретатора имеется также особый модуль __builtin__, объекты данного модуля создаются при запуске и не уничтожаются никогда, то есть время жизни таких объектов распространяется на всю программу. Если вы объявляете переменную в функции, цикле, операторе выбора, то она доступна только внутри блока, в котором была объявлена. Если вы хотите объявить глобальную переменную, то воспользуйтесь ключевым словом global. И последнее, о чём я хотел упомянуть в рамках данного раздела, это физические особенности переменных в Питоне. Переменные, естественно, хранятся в памяти компьютера, но имя переменной, это фактически символическая ссылка на ячейку памяти. Поэтому, когда вы присваиваете одной переменной другую, то они фактически указывают на один и тот же объект. При изменении одной переменной, изменяется и другая. Поэтому надо всегда быть осторожным с такого рода операциями.



Обращение к элементам классов.


Обращение через ссылку.

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

x.counter = 1 while x.counter < 10: x.counter = x.counter * 2 print x.counter del x.counter При присваивании переменной ссылки на метод класса, как например: xf = x.f while 1: print xf()

происходит следующее: возникает переменная, которая указывает на некую область памяти, где расположена самая первая исполняемая инструкция функции, но это место в памяти находится внутри класса, то есть при вызове такой функции будет происходить то же, что и при вызове MyClass.f().



Операции с файлами.


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

Метод open(имя, режим). Открывает файловый объект для последующего оперирования с ним. Функция принимает два параметра: строку, содержащую путь к файлу(может быть абсолютным или относительным текущего каталога или переменной окружения PATH) и режимом открытия файла. Режим открытия файла определяет допустимые операции доступа к файлу:

w Открыть файл для записи. Если такой файл уже существует, то его содержимое исчезает(если это возможно).
r Открыть файл только для чтения.
a Открыть файл для добавления, т.е. записи в конец файла. Предыдущее содержимое файла сохраняется.
r+ Открыть файл для записи/чтения, содержимое файла сохраняется.
w+ Открыть файл для записи/чтения, содержимое файла исчезает(см w).
r+b Открыть двоичный (если такие файлы поддерживаются операционной системой) файл для записи/чтения, содержимое файла сохраняется.
w+b Открыть двоичный файл для записи/чтения, содержимое файла исчезает(см w).
rb Открыть двоичный файл только для чтения.
wb Открыть двоичный файл для записи. Если такой файл уже существует, то его содержимое исчезает(если это возможно).

Двоичные файлы обеспечивают более быстрый доступ к данным, но бывают не переносимы и, к тому же, их сложно редактировать и просматривать. Текстовые файлы применимы в большинстве случаев, так как они переносимы, легко просматриваются и редактируются, но они медленнее двоичных и могут отображать корректно только символы набора ASCII.

>>> f=open('/tmp/workfile', 'w') >>> print f <open file '/tmp/workfile', mode 'w' at 80a0960>

Метод read([число_байт]). Данный метод читает из файла, открытого для чтения число байтов, указанных в качестве аргумента. Если метод вызывается без параметров, то читается весь файл, если файл был прочитан до конца(встретился символ EOF), то метод read() возвращает пустую строку:


>>> f.read() 'This is the entire file.\n' >>> f.read() ''

Метод readline() читает одну строку файла до символа перевода строки(включая сам символ \n). Если строка состоит только из символа перевода строки, то метод readline() возвращает пустую строку. Если в конце файла нет пустой строки с символом \n, то возвращаемый результат неопределён(этого допускать нельзя):

>>> f.readline() 'This is the first line of the file.\n'

>>> f.readline() 'Second line of the file\n'

>>> f.readline() ''

Метод readlines([размер_строки]) читает все строки файла в список. Читаются только законченные строки. Необязательный параметр размер_строки дополняет читает строку, и если она меньше указанной длины читает дальше, до достижения указанного числа символов, такой приём эффективен для чтения очень больших файлов построчно без необходимости размещения его в памяти:

>>> f.readlines()

['This is the first line of the file.\n', 'Second line of the file\n']

Метод write(строка) пишет в файл указанную строку, ничего при этом не возвращая:

>>> f.write('This is a test\n')

Метод seek(на_сколько_байт[, откуда]) перемещает указатель текущего положения файла на заданное количество байт от позиции, указанной вторым аргументом:

0(по умолчанию) 1 2
начало файла текущая позиция в файле конец файла
При этом, число байт для перемещения может быть как положительным, так и отрицательным(назад).

Метод tell() возвращает текущую позицию в файле:

>>> f=open('/tmp/workfile', 'r+')

>>> f.write('0123456789abcdef')

>>> f.seek(5) # Переход к шестому байту от начала

>>> f.read(1)

'5'

>>> f.seek(-3, 2) # Переход к третьему байту до конца файла

>>> f.read(1)

'd'

Метод close() закрывает объект файла, перенося все сделанные в нём изменения на диск, возвращая ресурсы операционной системе. Обязательно закрывайте все файлы, сто больше не используете, т.к. не закрытый файл может привести к потере данных. После закрытия обращение к объекту файла автоматически вызывает ошибку


Оператор del.


Данный оператор полезен для удаления объектов из памяти, когда они не нужны(после удаления объекта или переменной, вы не сможете больше к ним обращаться). Кроме того, оператор del может использоваться для удаления элемента из списка по его индексу или по промежутку:

>>> a [-1, 1, 66.6, 333, 333, 1234.5]

>>> del a[0] >>> a [1, 66.6, 333, 333, 1234.5]

>>> del a[2:4] >>> a [1, 66.6, 1234.5]



Опрос объектов Python


Мы упоминали слово "объект" уже несколько раз, но так и не дали ему определения. Объект в среде программирования во многом подобен объекту реального мира. Реальный объект имеет определенную форму, размер, вес и другие характеристики. Реальный объект может реагировать на свое окружение, взаимодействовать с другими объектами или выполнять задачу. Компьютерные объекты пытаются моделировать объекты, которые окружают нас в реальном мире, включая абстрактные объекты, как, например, документы, каталоги и бизнес-процессы.

Как и объекты реального мира, некоторые компьютерные объекты могут обладать общими характеристиками, допуская при этом небольшие вариации. Представьте книги, которые продаются в книжном магазине. У любого экземпляра книги может не хватать нескольких страниц, или они могут быть испачканы, и каждая копия книги может включать уникальный идентификационный номер. И, хотя каждая книга -уникальный объект, каждая книга с одним и тем же названием -лишь экземпляр оригинального шаблона; она сохраняет большинство характеристик оригинала.

Сказанное справедливо и в отношении объектно-ориентированных классов и экземпляров классов. Например, каждая строка Python обладает атрибутами, которые, как мы видели, были показаны функцией dir(). В приведенном выше примере мы определили наш собственный класс Person, который действует как шаблон для создания отдельных экземпляров Python, у каждого из которых свои собственные значения имени и возраста, но общая для всех возможность представиться. Это и есть объектно-ориентированное программирование.

На языке программирования объекты, следовательно - это нечто, что имеет тождественность и значение, а также определенный тип, обладает некими характеристиками и определенным поведением. Объекты наследуют многие из своих атрибутов от одного или более родительских классов. Кроме ключевых слов и специальных символов (подобных таким операторам, как +, -, *, **, /, %, <, > и т. д.) все на Python является объектом. Python выпускается с широким набором типов объектов: строки, целые числа, числа с плавающей точкой, списки, кортежи, словари, функции, классы, экземпляры классов, модули, файлы и т.п.

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

Какое у тебя имя?

Что ты за объект?

Что ты знаешь?

Что ты можешь?

Кто твои предки?



Ошибки и исключения.


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



Основные элементы программирования.


Если вы читали всё, что я написал до этого пункта, то можете подумать, что Питон – это очень убогий язык. Но в действительности это не так. Вы можете писать на Питон программы любой сложности. Например, нам надо найти первые числа Фиббоначи(числа Фиббоначи характеризуются тем, что каждое следующее число Фибоначчи равно сумме двух предыдущих):

>>> # Числа Фибоначчи: ... # Сумма двух предыдущих определяет следующее ... a, b = 0, 1#Два первых числа >>> while b < 10:#Цикл ниже выполняется пока число Фибоначчи меньше 10 ... print b ... a, b = b, a+b ...

Результат выполнения программы:

1

1

2

3

5

8

Эта программа демонстрирует многие возможности Питона.

Первая строка демонстрирует присваивание двум переменным двух различных значений

Вторая строка демонстрирует заголовок цикла while(пока). Этот цикл (после двоеточия) обозначается отступом и выполняется пока условие в заголовке цикла(b < 10) является верным(или не равно нулю, например цикл while 0 выполняться не будет, а цикл while 1 будет выполняться бесконечно и зациклит программу, если внутри цикла не предусмотрено какое-либо условие выхода(плохой стиль программирования)). Попробуйте изменять число в цикле, и вы увидите, как будет изменяться верхнее значение переменной b. Кроме этого, в цикле while могут использоваться строки, списки, при этом всё, имеющее нулевую длину, считается нулём, а всё, имеющее ненулевую длину, считается истиной. В операторе while вы можете использовать следующие операции: < (меньше), > (больше), ==(равно), <= (меньше или равно), >= (больше или равно) и != (не равно).

Третья строка выводит на экран значение переменной b. В этом операторе вы можете использовать любые переменные, списки, строки(строки выводятся без кавычек) разделённые друг от друга пробелом. Поэтому вы можете отформатировать вывод так, как вам это надо.

Четвёртая строка также демонстрирует присваивание двум переменным двух значений. Самое важное – отметить, что вначале оцениваются выражения справа(т.е b, a + b), а затем эти выражения подставляются в левую часть.

Кроме этого, отметим, что логические части программы(такие как, например, тело цикла) отмечаются отступами от основного уровня текста программы, а не специальными символами({} в Си и begin end в Паскале). Заметьте также, что после заголовка следует символ двоеточия.

Приведём ещё некоторые примеры несложных программ:

>>> i = 256*256 >>> print 'Значение i`, i Значение i 65536

Разделение переменных запятой позволяет подавить переход на новую строку при выводе.

>>> a, b = 0, 1 >>> while b < 1000: ... print b, ... a, b = b, a+b ... 1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987

Заметьте, что перевод строки осуществляется интерпретатором автоматически, когда следующая переменная не умещается в текущей строке.

|



Основные операторы.


Оператор if.

Если вы знакомы с английским языком, то легко поймёте, что слово if переводится как “если”. Этот оператор выполняет блок кода, следующий после него с отступами, только в случае, если выражение в его заголовке не нуль(т.е. верно).

>>> x = int(raw_input("Введите целое число: ")) >>> if x < 0: ... x = 0 ... print 'Отрицательное число стало нулём' ... elif x == 0: ... print 'Это число - нуль' ... elif x == 1: ... print 'Это число - 1' ... else: ... print `Это число больше единицы' ...

Далее могут быть ещё операторы elif(аналог case в Си). Оператору if, как видно, сопутствуют операторы else(“иначе” - блок кода выполняется если условие в заголовке if приняло нулевое значение, т.е стало ложным) и elif(“иначе если” - блок кода выполняется если условие в заголовке if приняло нулевое значение, т.е стало ложным, а значение в заголовке данного оператора стало ненулевым, т.е истинным).

Оператор for.

Оператор for несколько необычен в Питоне, он имеет вид for некая_переменная in некий_диапазон. Блок кода после заголовка выполняется, пока некая_переменная принадлежит некому_диапазону(причём этот диапазон может быть списком, числовой последовательностью, массивом каких-либо значений):

>>> # Определим какие-то строки: ... a = ['Linux', 'Open', 'Office'] >>> for x in a: ... print x, len(x)

Результат работы:

Linux 5

Open 4

Office 6

Довольно опасно менять в теле цикла for значение диапазона(это может повлечь весьма странную его работу, зацикливание и усложняет понимание программы), кроме случаев, когда в роли диапазона выступает список. В этом случае, можно сделать простое копирование списков, удвоение элементов в списке:

>>> for x in a[:]: # здесь мы делаем копию всего списка a в переменной x ... if len(x) > 6: a.insert(0, x)#Если длина строки списка больше 6 ...

>>> a

Результат работы: [Linux , Open, Office, Office]

Для задания диапазона в форме арифметической прогрессии (1 2 3 4 5 6...) удобно пользоваться функцией range().
Она имеет три формы, рассмотрим на примерах все:

>>> range(10) [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

Эта форма возвращает все целые числа в диапазоне от 0 до числа 10, не включая само число 10.

>>> range(5, 10) [5, 6, 7, 8, 9]

Эта форма возвращает все целые числа в диапазоне от 5 до числа 10, не включая само число 10, но включая начальное число 5.

>>> range(0, 10, 3) [0, 3, 6, 9]

>>> range(-10, -100, -30) [-10, -40, -70]

Эта форма возвращает все целые значения в диапазоне от начального до конечного с шагом, заданным третьим параметром. Причём, если, например, вы попробуете ввести range(1, 100, -1), то очевидно, что чисел в этом диапазоне нет, то есть результатом будет пустое множество([]).

Если вы хотите задать диапазон через количество элементов в списке, то следует воспользоваться функцией range в сочетании с функцией len:

>>> a = [`Linux', 'is', 'the', 'best', 'system'] >>> for i in range(len(a)): ... print i, a[i]#Обращение к элементу списка по его индексу ...

0 Linux

1 is

2 the

3 best

4 system

Прерывание и продолжение циклов for и while.

Для немедленного выхода из цикла можно использовать оператор break(хотя некоторые программисты считают применение break плохим стилем), а для продолжения цикла, но со следующим значением переменной(т.е следующей итерации цикла) можно использовать оператор continue. Всё сказанное выше можно показать на примере поиска простых чисел:

>>> for n in range(2, 10):#Задаёт верхнюю границу

... for x in range(2, n): ... if n % x == 0:#Если n делится на x без остатка и n не простое число ... print n, 'равно', x, '*', n/x ... break#Выход из цикла по x, следующее n ... else: ... # если n не разделилось без остатка ни на одно x от 2 до n, то ... print n, 'простое число' ...

Результат работы: 2 простое число

3 простое число

4 равно 2 * 2

5 простое число

6 равно 2 * 3

7 простое число

8 равно 2 * 4

9 равно 3 * 3

Оператор pass.

pass означает, что не надо выполнять никаких действий.Этот оператор может использоваться, когда наличие какого-либо кода необходимо синтаксически, но не нужно по логике работы программы:

>>> while 1: ... pass # Бесконечный цикл, ничего не делающий: ждём прерывания с клавиатуры ...


Особенности операторов сравнения.


Теперь, когда мы познакомились со списками, пришёл черёд изучить дополнительные сведения об операторах сравнения. Операторы сравнения (>; <; ==; !=; >=; <=)имеют в выражениях приоритет оценки ниже арифметических операторов, то есть в выражении a + b > c + d * e будут оценены вначале арифметические операции, а затем их результаты будут сравниваться. Операторы сравнения могут комбинироваться друг с другом, например, в выражении “a < b == c” операторы сравнения оцениваются слева направо, так как имеют одинаковый приоритет. Выражения с операторами сравнения могут объединяться с помощью логических операций or(или), and(и) и not(не). Значение логических операторов ясно из названия, поэтому комментарии здесь излишни. Приоритет логических операторов ниже, чем к операторов сравнения, самый высокий приоритет у оператора not, самый низкий – у оператора or. Поэтому, при оценке логических выражений, включающих в себя операторы различного приоритета, встретившееся выражение false может вызвать то, что операторы с более низким приоритетом обработаны не будут.

Для списков существуют особые операторы сравнивания:

оператор in(not in) оценивает, входит ли заданный элемент в последовательность;

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

Результат сравнения может быть присвоен переменной логического типа(принимает значения true и false). В отличие от языка Си, в Питоне не допускаивается присваивание при сравнении, что исключает ошибку, распространённую в языке Си и связанную с употреблением =, вместо ==.



Отсутствующие эквиваленты


В itertools нет ireduce(), хотя это могло бы показаться естественным; возможная Питоновская реализация такова:

Листинг 1. Пример реализации ireduce()

def ireduce(func, iterable, init=None): if init is None: iterable = iter(iterable) curr = iterable.next() else: curr = init for x in iterable: curr = func(curr, x) yield curr

Случай использования ireduce() подобен варианту с reduce(). Например, предположим, что вы хотите суммировать список чисел, находящихся в большом файле, но остановиться, когда выполняется условие. Вы могли бы контролировать текущий итог с помощью:

Листинг 2. Добавление списка чисел и подведение итога

from operator import add from itertools import * nums = open('numbers') for tot in takewhile(condition, ireduce(add, imap(int, nums)): print "total =", tot

Более реальный пример, вероятно, что-нибудь вроде применения потока событий к объекту, поддерживающему внутреннее состояние, как, например, к элементу управления графического интерфейса пользователя. Но даже простой вышеприведенный пример демонстрирует итераторные комбинаторы в стиле функционального программирования.



Пакеты.


Несколько модулей в Питоне могут быть объединены по функциональному или какому-нибудь другому признаку. Для этого используются пакеты. Суть пакетов такова: вы объединяете несколько модулей по некому признаку в пакет, затем могут появиться другие пакеты с модулями, организовать структуру пакетов можно по принципу дерева. Принципу дерева подчиняются также папки в файловых системах. То есть пакеты подобны папкам на диске:

Sound/ Корневой пакет __init__.py Инициализация звуковой библиотеки Formats/ Подпакет звуковых форматов __init__.py wavread.py wavwrite.py aiffread.py aiffwrite.py auread.py auwrite.py ... Effects/ Подпакет звуковых эффектов __init__.py echo.py surround.py reverse.py ... Filters/ Подпакет фильтров __init__.py equalizer.py vocoder.py karaoke.py ...

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

Файл __init__.py нужен интерпретатору для того, чтобы расценивать каталог на диске, как пакет. Данный файл может быть пустым, но может содержать исполняемый код, для инициализации переменной __all__.

Из пакетов можно импортировать отдельные модули несколькими путями:

1. import Sound.Effects.echo

Загрузка модуля echo. Далее должно указываться полное имя модуля.:

Sound.Effects.echo.echofilter(input, output, delay=0.7, atten=4)

2. from Sound.Effects import echo

Также загрузка модуля echo, но теперь к нему можно обращаться без указания имени модуля:

echo.echofilter(input, output, delay=0.7, atten=4)

3. from Sound.Effects.echo import echofilter

Прямой импорт объекта из пакета. Теперь вы можете обращаться к функции echofilter() непосредственно через её имя:

echofilter(input, output, delay=0.7, atten=4)

Из пакетов можно также импортировать все модули. Но трюк с import * не пройдёт на некоторых операционных системах(DOS, Windows, MAC), так как в них не различается регистр у файлов и папок, а так как в Питоне регистр различается, то это может привести к непредсказуемым именам модулей(Echo, echo, ECHO, eChO). Поэтому для надёжности лучше создавать список модулей пакета в файле __init__.py. Для этого присвойте переменной __all__ список всех имён модулей(строковый тип элемента списка). Такой подход обеспечит корректную интерпретацию пакета на любой системе.

__all__ = ["echo", "surround", "reverse"]

А в самом коде можно теперь применять import *:

from Sound.Effects import *

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



Передача в функцию переменного числа аргументов.


Часто используемым приёмом в программировании является передача в функцию переменного числа аргументов. Для этого в Питоне можно воспользоваться символом * перед списком аргументов переменной длины. Аргументы в теле функции могут быть разделены(см. выше). Перед списком аргументов может следовать(необязательно) один или несколько обязательных аргументов:

def fprintf(file, format, *args): file.write(format % args)



Переходим на функциональное программирование?


В предыдущей статье "Функциональное программирование на Python" были освещены основные понятия функционального программирования (ФП). В этой статье мы попытаемся немного углубиться в эту богатейшую концептуальную область. Библиотека Xoltar Toolkit Брина Келлера (Bryn Keller) окажет нам в этом неоценимую помощь. Основные возможности ФП Келлер представил в виде небольшого эффективного модуля на чистом Python. Помимо модуля functional, в Xoltar Toolkit входит модуль lazy, поддерживающий структуры, вычисляемые "только когда это необходимо". Множество функциональных языков программирования поддерживают отложенное вычисление, поэтому эти компоненты Xoltar Toolkit

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



Первое знакомство с классами.


В Питоне есть все средства поддержки объектно-ориентированного подхода – это технология классов. Классы могут содержать в себе самые различные элементы: переменные, константы, функции и другие классы. Типичное описание класса в Питоне выглядит так:

class имя_класса: элемент_класса_1 . . . элемент_класса_n

Объявление класса напоминает использование ключевого слова def для функции, пока класс не объявлен, использовать его запрещено. Класс может быть описан внутри функции или структуры if, но всё же желательнее описывать класс вне программных структур, то есть в теле программы, а ещё лучше описать все классы в самом начале программы, так как это облегчает чтение программы. Класс, будучи объявлен, создаёт в программе новую область действия, поэтому всё, описанное внутри класса, включается в область действия класса и является недоступным извне. Обычно класс состоит в основном из функций элементов, они определяются внутри класса словом def. Функции-элементы класса имеют некоторые особенности списка аргументов(об этом будет сказано далее).

К объектам классов в Питоне можно обращаться двумя способами: ссылкой на элемент(имя_класса.имя_элемента) и через присвоение переменной класса через функцию(переменная = имя_класса()), например:

class MyClass:

"Простой класс"

i = 12345

def f(self):

return 'Привет мир'

x = MyClass()

Такое объявление присваивает переменной х объект класса MyClass и теперь ко всем элементам данного класса можно обращаться через данную переменную. Причём область действия данного объекта совпадает с областью действия переменной х. Следует отметить особый параметр self, который передаётся функции класса. Этот параметр является типичным для функций-элементов класса, так как содержит ссылку на класс, которому принадлежит данная функция и позволяет обращаться к другим членам класса.

При создании нового объекта, создаётся пустой объект, но часто такое поведение оказывается неправильным и неудобным. Тогда к вам на помощь может прийти функция-элемент __init__() класса. Подобно функции __init__() модуля, она вызывается при создании объекта класса и выполняет инициализацию полей(переменных) класса. Пример применения функции __init__():

def __init__(self): self.data = []

Функция __init__() может принимать сколько угодно параметров, но при создании экземпляра класса через функцию, необходимо указать все параметры, кроме, конечно, self:

>>> class Complex: ... def __init__(self, realpart, imagpart): ... self.r = realpart ... self.i = imagpart ... >>> x = Complex(3.0, -4.5) >>> x.r, x.i

(3.0,-4.5)



Питоновское прогрессирующее раздвоение личности


В отношении Python к функциональному программированию есть что-то шизофреническое. С одной стороны, многие разработчики Python недооценивают традиционные функции функционального программирования: map(), filter() и reduce() - обычно рекомендуя использовать вместо них списочные включения (list comprehensions). Но весь модуль itertools составлен из функций точно такого же вида и просто оперирует над "отложенными последовательностями" (итераторами), а не над законченными последовательностями (списками, кортежами). Более того, в Python 2.3 отсутствует синтаксис для "итераторных включений" (iterator comprehensions), которые казалось бы имеют те же мотивы, что и списочные включения.

Я подозреваю, что Python в конечном счёте разовьет некую форму итераторных включений, но это зависит от нахождения подходящего естественного синтаксиса для них. Между тем, у нас имеется ряд удобных комбинаторных функций в модуле itertools. Вообще каждая из этих функций принимает некоторые параметры (обычно включая некоторые базовые итераторы) и возвращает новый итератор. Например, функции ifilter(), imap() и izip() полностью эквивалентны соответствующим встроенным функциям, у которых отсутствует начальное i.



Поднятие объектно-ориентированного программирования на новый уровень


Большинство читателей уже знакомо с концепциями объектно-ориентированного программирования: наследованием, инкапсуляцией, полиморфизмом. Но создание объектов заданного класса с определенными родителями обычно представляется исходно заданной операцией. Оказывается, что ряд новых программных конструкций становится либо более простым, либо вообще хоть сколько-нибудь возможным, если вы можете настраивать процесс создания объектов. Метаклассы разрешают определенные типы "аспектно-ориентированного программирования"; например, можно усилить классы такими характеристиками, как возможности трассировки, объектная персистентность, регистрация исключений и так далее.



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


1. T.е. вычисление логического выражения заканчивается сразу, как только становится известен его логический результат. Стоит также заметить, что в Python, так же как и в Lisp, значением логического выражения является не true/false, а значение последнего вычисленного подвыражения - например, 4 and "Hello!" or 2*2 будет иметь значение "Hello!". прим. перев.

2. monadic_print() может быть реализована в полностью функциональном стиле, без использования утверждения print: monadic_print = lambda x: sys.write(str(x) + '\n') and x

прим. перев.
3. Следует обратить внимание, что пример работает только в том случае, если переменная echo_FP глобальна. Это связано с тем, что в Python всех версий до 2.0 включительно отсутствует статическая вложенность области действия имен. Любое имя, встретившееся в контексте функции или метода, ищется сначала среди локальных имен функции, а потом сразу среди глобальных имен (затем среди встроенных имен). Это отличается от логики языков со статическими областями действия имен (C, C++, Pascal, etc.), где имя последовательно ищется во всех объемлющих блоках. Из этого, в частности, следует, что рекурсивный вызов lambda-функции, привязанной к неглобальному имени, в Python версии меньше 2.1 невозможен. В Python 2.1 введены опциональные статические области действия. Таким образом, начиная с версии 2.1 Python можно рассматривать и как полноценный FP-язык (помимо всего прочего). Вышеприведенный комментарий относится почти ко всем функциональным примерам в статье. прим.
Оригинальный текст статьи можно посмотреть здесь:

Присвоение значений


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

#---- FP-сессия Python с переприсваиванием приводит к неприятностям ---#

>>> car = lambda lst: lst[0]

>>> cdr = lambda lst: lst[1:]

>>> sum2 = lambda lst: car(lst)+car(cdr(lst))

>>> sum2(range(10)) 1

>>> car = lambda lst: lst[2]

>>> sum2(range(10)) 5

К несчастью, одно и то же выражение sum2(range(10))

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

К счастью, модуль functional

предоставляет класс Bindings (предложенный Келлеру автором), предотвращающий такое переприсваивание (по крайней мере, случайное; Python не препятствует решительному программисту намеренно нарушать правила). Хотя использование Bindings

требует немного дополнительного кода, это оградит вас от случайностей. Келлер обозначает экземпляр класса Bindings как let

(я полагаю, из-за зарезервированного слова let

в ML-языках программирования).

Например, мы могли бы сделать следующее:

#------- FP-сессия Python с защитой от переприсваивания -------#

>>> from functional import *

>>> let = Bindings()

>>> let.car = lambda lst: lst[0]

>>> let.car = lambda lst: lst[2]

Traceback (innermost last):

File "", line 1, in ?

         File "d:\tools\functional.py", line 976, in __setattr__



           raise BindingError, "Binding '%s' cannot be modified." % name

functional.BindingError:  Binding 'car' cannot be modified.

>>> let.car(range(10)) 0

Разумеется, реальная программа должна перехватить и обработать исключение BindingError, однако сам факт его возбуждения позволяет избежать целого класса проблем.

Помимо класса Bindings, functional

содержит функцию namespace, предоставлюющую доступ к пространству имен (на самом деле, к словарю) из экземпляра класса Bindings. Это очень удобно, если вам нужно вычислить выражение в (неизменяемом) пространстве имен, определенном в Bindings. Функция eval() в Python позволяет проводить вычисление в пространстве имен. Следующий пример поясняет сказанное:



#----- FP-сессия Python, использующая неизменяемые пространства имен -----#

>>> let = Bindings()      # "Real world" function names

>>> let.r10 = range(10)

>>> let.car = lambda lst: lst[0]

>>> let.cdr = lambda lst: lst[1:]

>>> eval('car(r10)+car(cdr(r10))', namespace(let))

>>> inv = Bindings()      # "Inverted list" function names

>>> inv.r10 = let.r10

>>> inv.car = lambda lst: lst[-1]

>>> inv.cdr = lambda lst: lst[:-1]

>>> eval('car(r10)+car(cdr(r10))', namespace(inv))

17


Проблемы, решаемые магией


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

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

Первый, и вероятно более общий случай, это когда в период проектирования вы не знаете точно, что нужно делать классу. Очевидно, что у вас появится представление об этом, но некая определенная деталь может зависеть от информации, которая станет доступной позднее. Само "позднее" может быть двух видов: (а) когда библиотечный модуль будет использоваться приложением; (б) во время исполнения, когда будет существовать некая ситуация. Этот категория близка к тому, что часто называют "Аспектно-ориентированным программированием" (АОП). Продемонстрируем, что мы имеем в виду на следующем элегантном примере:



Листинг 7. Конфигурирование метакласса во время исполнения

% cat dump.py #!/usr/bin/python import sys if len(sys.argv) > 2: module, metaklass = sys.argv[1:3] m = __import__(module, globals(), locals(), [metaklass]) __metaclass__ = getattr(m, metaklass)

class Data: def __init__(self): self.num = 38 self.lst = ['a','b','c'] self.str = 'spam' dumps = lambda self: `self` __str__ = lambda self: self.dumps()

data = Data() print data

% dump.py <__main__.Data instance at 1686a0>

Как вы могли ожидать, это приложение выводит весьма общее описание объекта data (условный объект экземпляра). Однако, если аргументы времени исполнения передаются в приложение, можно получить несколько отличный результат:



Листинг 8. Добавление метакласса внешней сериализации

% dump.py gnosis.magic MetaXMLPickler <?xml version="1.0"?> <!DOCTYPE PyObject SYSTEM "PyObjects.dtd"> <PyObject module="__main__" class="Data" id="720748"> <attr name="lst" type="list" id="980012" > <item type="string" value="a" /> <item type="string" value="b" /> <item type="string" value="c" /> </attr> <attr name="num" type="numeric" value="38" /> <attr name="str" type="string" value="spam" /> </PyObject>


В этом частном примере применяется стиль сериализации gnosis.xml.pickle, но текущая версия пакета gnosis.magic также содержит метаклассы сериализаторов MetaYamlDump, MetaPyPickler и MetaPrettyPrint. Кроме того, пользователь "приложения" dump.py может потребовать использование любого желаемого "MetaPickler" из любого пакета Python, который его определяет. Соответствующий метакласс, предназначенный для этой цели, будет выглядеть приблизительно так:

Листинг 9. Добавление атрибута с метаклассом

class MetaPickler(type): "Metaclass for gnosis.xml.pickle serialization" def __init__(cls, name, bases, dict): from gnosis.xml.pickle import dumps super(MetaPickler, cls).__init__(name, bases, dict) setattr(cls, 'dumps', dumps)

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

Возможно, наиболее общее использование метаклассов схоже с применением MetaPicklers: добавление, удаление, переименование или подстановка методов вместо методов, определенных в созданном классе. В нашем примере "встроенный" метод Data.dump() заменяется другим методом, внешним по отношению к приложению, во время создания класса Data (и, следовательно, в каждом последующем экземпляре).


Программное возбуждение исключений.


Если вы пишете библиотеки функций, то одним из способов сообщить вызывающей программе о том, что случилась непредвиденная ситуация, является возбуждения исключений. Для этой цели используется оператор raise, который возбуждает заданное исключение. Оператор raise имеет следующий синтаксис: raise имя_исключения[, аргумент_исключения, аргумент_исключения ...]. Если raise вызывается без аргументов, то она возбуждает повторно самое последнее исключение:

>>> raise NameError, 'HiThere' Traceback (most recent call last):

File "<stdin>", line 1, in ?

NameError: HiThere

raise без параметров часто используется в промежуточных обработчиках исключений:

>>> try: ... raise NameError, 'HiThere' ... except NameError: ... print 'Произошла ошибка!' ... raise ... Произошла ошибка!

Traceback (most recent call last):

File "<stdin>", line 2, in ?

NameError: HiThere

В Питоне можно создавать собственные типы исключений в виде классов. Но эта тема будет рассмотрена в разделе классы и объектно-ориентированное программирование, потому что все исключения – это классы.



Производные классы


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



Листинг 34. Не ты ли мой предок?

>>> print issubclass.__doc__ issubclass(C, B) -> Boolean

Return whether class C is a subclass (i.e., a derived class) of class B.

>>> class SuperHero(Person): # SuperHero наследуется из Person... ... def intro(self): # но с новым SuperHero intro ... """Return an introduction.""" ... return "Hello, I'm SuperHero %s and I'm %s." % (self.name, self.age) ... >>> issubclass(SuperHero, Person) 1 >>> issubclass(Person, SuperHero) 0 >>>



Синтаксические ошибки.


Ошибки такого типа обычно находятся интерпретатором:

>>> while 1 print 'Hello world' File "<stdin>", line 1, in ?

while 1 print 'Hello world'

^

SyntaxError: invalid syntax #Неверный синтаксис команды

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



Словари.


Во всех рассмотренных последовательностях обращаться к отдельным элементам нужно было по индексу. Иную форму организации последовательности представляют словари. В словарях для доступа к отдельным его элементам используются ключевые индексы, подобные индексам в базах данных. Индексом может быть любой неизменяемый объект, такой как строка, число, константный список(такой список может содержать только строки, числа или другие константные списки). В тексте программы словари задаются фигурными скобками {} с элементами словаря. Каждому элементу словаря должен соответствовать определённый индекс, который отделяется от элемента двоеточием(“индекс:значение”). К элементам словаря можно обращаться по соответствующим им индексам. При обращении к несуществующему индексу возникает ошибка. Чтобы узнать список всех индексов словаря, можно воспользоваться методом keys(), которая возвращает все индексы словаря в случайном порядке(но вы можете отсортировать индексы функцией sort()). Чтобы проверить наличие индекса в словаре, можно использовать метод has_key(). Вот простой пример использования словаря:

>>> tel = {'Ваня': 4098, 'Коля': 4139} >>> tel['Андрей'] = 4127 >>> tel {'Коля': 4139, 'Андрей': 4127, 'Ваня': 4098}

>>> tel['Ваня'] 4098

>>> del tel['Коля'] >>> tel['Дима'] = 4127 >>> tel {'Андрей': 4127, 'Дима': 4127, 'Ваня': 4098}

>>> tel.keys() ['Андрей', 'Дима', 'Ваня']

>>> tel.has_key('Ваня') 1



Списки.


Списки широко распространены в Питоне и имеют множество методов манипулирования(метод отделяется от имени списка точкой: имя_списка.метод()):

append(x) вставляет в конец списка элемент х. Эквивалентно a[len(a):] = [x].

extend(L) добавляет списку в конец все элементы списка L. Эквивалентно a[len(a):] = L.

insert(i, x) вставляет элемент x в позицию перед индексом i в списке(для вставки элемента в начало списка воспользуйтесь insert(0, x)).

remove(x) удаляет первое вхождение x в список, вызывает ошибку если элемент x не найден.

pop(i) удаляет элемент с индексом i и возвращает его. Если вызвать pop() без параметров, то будет возвращён и удалён последний элемент списка.

index(x) возвращает индекс первого вхождения элемента х в список, вызывает ошибку если элемент x не найден.

count(x) возвращает количество вхождений элемента x в список.

sort() сортирует элементы списка по возрастанию.

reverse() переворачивает список в обратном порядке.

Примеры использования методов списков:

>>> a = [66.6, 333, 333, 1, 1234.5] >>> print a.count(333), a.count(66.6), a.count('x') 2 1 0

>>> a.insert(2, -1) >>> a.append(333) >>> a [66.6, 333, -1, 333, 1, 1234.5, 333]

>>> a.index(333) 1

>>> a.remove(333) >>> a [66.6, -1, 333, 1, 1234.5, 333]

>>> a.reverse() >>> a [333, 1234.5, 1, 333, -1, 66.6]

>>> a.sort() >>> a [-1, 1, 66.6, 333, 333, 1234.5]



Сравнение списков.


Сравнение списков несколько отличается от сравнения простых числовых значений. Во-первых, списки должны быть одинакового типа. Во-вторых сравнение идёт в лексикографическом порядке, т.е оцениваются вначале первые элементы последовательностей, если они не равны, то далее возвращается результат(>;<;!=), иначе оценивается следующая пара элементов. Последовательности будут равны только в том случае, если все их элементы будут соответственно равны. Кроме этого, более длинная последовательность будет всегда больше более короткой.Строки сравниваются, учитывая порядок символов в строках в таблице ASCII. Приведём примеры сравнения последовательностей:

(1, 2, 3) < (1, 2, 4) [1, 2, 3] < [1, 2, 4] 'ABC' < 'C' < 'Pascal' < 'Python' (1, 2, 3, 4) < (1, 2, 4) (1, 2) < (1, 2, -1) (1, 2, 3) == (1.0, 2.0, 3.0)

2, ('aa', 'ab')) < (1, 2, ('abc', 'a'), 4)

При этом, допускается сравнивание элементов различного типа(хотя по-моему это сильно усложняет понимание программы), при этом строка всегда больше списка, константный список больше строки и.т.д. Различные числовые типы сравниваются путём приведения их к типу с точкой.



Стандартные модули Питона.


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

В модулях содержатся некоторые функции, позволяющие настраивать интерпретатор. Это в основном функции и переменные модуля sys. Например, переменные sys.ps1 и sys.ps2 хранят в себе приглашение интерпретатора:

>>> import sys >>> sys.ps1 '>>> '

>>> sys.ps2 '... '

>>> sys.ps1 = '[]# ' []# print 'Ой!' Ой! []#

Список sys.path хранит в себе пути поиска библиотек интерпретатором:

>>> import sys >>> sys.path.append('/home/guido/lib/python')

Встроенная функция dir([имя_модуля]) позволяет вывести все загруженные модули, может также вывести все объекты какого-либо конкретного модуля(с указанием имени модуля, в качестве аргумента dir()):

>>> import fibo, sys >>> dir(fibo) ['__name__', 'fib', 'fib2']

>>> 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',

'maxint', 'maxunicode', 'modules', 'path', 'platform', 'prefix', 'ps1',

'ps2', 'setcheckinterval', 'setdlopenflags', 'setprofile',


'setrecursionlimit', 'settrace', 'stderr', 'stdin', 'stdout', 'version',

'version_info', 'warnoptions']

Встроенные функции Питона содержатся в модуле __builtin__( импортировать его для использования функций не нужно):

>>> import __builtin__ >>> dir(__builtin__) ['ArithmeticError', 'AssertionError', 'AttributeError',

'DeprecationWarning', 'EOFError', 'Ellipsis', 'EnvironmentError',

'Exception', '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', 'TypeError', 'UnboundLocalError',

'UnicodeError', 'UserWarning', 'ValueError', 'Warning',

'ZeroDivisionError', '_', '__debug__', '__doc__', '__import__',

'__name__', 'abs', 'apply', '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']


Строки:


В Питоне строки могут заключаться как в двойные кавычки, так и в одинарные – это не играет никакой роли(если символ кавычек внутри самой строки, то перед ним ставится \):

>>> 'привет, Питон' 'привет, Питон'

>>> 'привет, \”Питон\”' 'привет, “Питон”'

>>> "doesn't" "doesn't"

>>> '"Yes," he said.' '"Yes," he said.'

>>> "\"Yes,\" he said." '"Yes," he said.'

>>> '"Isn\'t," she said.' '"Isn\'t," she said.'

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

Если строка слишком длинная, то можно уместить её в нескольких строках путём указания в конце символа \, например:

а = “Это очень длинная \ строка, содержащая \ 3 строчки\n” >>>print a

Это очень длинная строка, содержащая 3 строчки

Символ \n является так называемым управляющим символом, он переводит строку. Чтобы подавить использование управляющих символов в строке, поставьте пред открывающей кавычкой символ r(сырой формат строки).

Можно также окружать строки тройными кавычками в операторе print(вывести строку в stdout), например:

print """ Usage: thingy [OPTIONS] -h Display this usage message -H hostname Hostname to connect to """ выведет следующее:

Usage: thingy [OPTIONS] -h Display this usage message

-H hostname Hostname to connect to

При этом внутри тройных кавычек могут следовать любые символы, они будут выведены как есть.

Со строками можно производить некоторые простые операции, такие как склеивание(+) и повторение строк(*):

>>> word = 'Да' + 'Нет' >>> word 'ДаНет'

>>> '<' + word*5 + '>' '< ДаНетДаНетДаНетДаНетДаНет >'

В Питоне, как и в С, существует индексация строк первый символ строки имеет индекс 0. Индексы обозначаются в квадратных скобках. Вместо индекса можно использовать интервал в форме begin:end, по умолчанию begin – начало строки, а end – её конец.


>>> word[4] 'Е'

>>> word[0:2] 'Да'

>>> word[2:4] 'Не'

Внимание: в Питоне нельзя непосредственно изменять символы в строке через индексы – это вызовет ошибку интерпретатора. Можно только читать значение символа, что позволяет бы­стро создавать новые строки по частям других:

>>> 'x' + word[1:]#Все символы начиная со 2-го 'хаНет'

>>> 'Не' + word[4]#Только пятый символ 'Нее'

Все индексы начинают отсчёт с начала строки, однако применение отрицательных индексов позволяет вести отсчёт с конца строки:

>>> word[-1] #Последний символ 'т'

>>> word[-2] #Предпоследний символ 'е'

>>> word[-2:] # Два последних символа 'ет`

>>> word[:-2] #Все, кроме 2-х последних символов 'ДаН'

Представим, как работают индексы:

Д а Н е т
0 1 2 3 4
-5 -4 -3 -2 -1
Если вы задаёте индексы, будте внимательны к этим важным деталям. Кроме того, если вы напишете word[:-100], то индекс будет урезан до -5(то есть выведена вся строка), но если вы укажите несуществующий элемент в качестве индекса 1-го символа word[-100], то будет выдана ошибка.

Полезный совет: функция len(строка) возвращает длину переданной строки

>>>len(word) 5


Строки документации


Возможно, вы заметили в наших многочисленных примерах с dir() один атрибут среди множества других - __doc__. Этот атрибут - строка, которая содержит комментарии, описывающие объект. Python называет ее строкой документации, или docstring, и вот, как она работает. Если первая директива определения модуля, класса, метода или функции - строка, то эта строка оказывается связанной с объектом в качестве его атрибута __doc__. Посмотрите, например, на строку документации для объекта __builtins__. Воспользуемся Питоновской директивой print, чтобы выходные данные было легче читать, поскольку строки документации часто содержат встроенные разделители строк (\n):



Листинг 24. Строка документации модуля

>>> print __builtins__.__doc__ # Модуль докстроки Built-in functions, exceptions, and other objects.

Noteworthy: None is the `nil' object; Ellipsis represents `...' in slices.

И вновь, Python поддерживает даже строки документации для классов и методов, которые определены интерактивно в оболочке Python. Давайте рассмотрим строки документации для нашего класса Person и его метода intro:



Листинг 25. Строки документации класса и метода

>>> Person.__doc__ # Докстрока класса 'Person class.' >>> Person.intro.__doc__ # Докстрока метода класса 'Return an introduction.'

Поскольку строки документации предоставляют такую ценную информацию, многие среды разработки Python включают способы автоматического отображения строк документации для объектов. Давайте рассмотрим еще одну строку документации - для функции dir():



Листинг 26. Строка документации функции

>>> print dir.__doc__ # Function docstring dir([object]) -> list of strings

Return an alphabetized list of names comprising (some of) the attributes of the given object, and of attributes reachable from it:

No argument: the names in the current scope. Module object: the module attributes. Type or class object: its attributes, and recursively the attributes of its bases. Otherwise: its attributes, its class's attributes, and recursively the attributes of its class's base classes.



Строки юникода.


Строки Юникода позволяют поддерживать символы всех алфавитов, древних и ныне существующих. Дело в том, что обычные строки могут содержать до 256 различных символов, это очень ограничивало алфавит компьютера. Со введением юникода в строке может содержаться до 65536(!) различных символов. Питон, как язык, поддерживающий всё новое в компьютерном мире, поддерживает юникод. Строки юникода обозначаются символом u перед открывающей кавычкой., например u”РоссияUSA”. В юникоде также имеются управляющие символы, но обозначаются они по-другому в формате \0000, где 0000 некий управляющий символ(например \0020 – пробел). Полный список управляющих символов юникода можно найти на сайте . Если вы не хотите отображение управляющих символов, поставьте перед открывающей кавычкой символы ur. Все операции со строками обычного формата применимы к строкам юникода. То есть внутри Питона юникод и обычный текст во многом схожи, и вы можете использовать и то и другое представление строк(учтите, однако, что текст в 2 раза компактнее юникода, зато юникод способен воспринимать любой алфавит). Поэтому, что вам нужно употреблять, зависит от конкретной задачи(переносимость или компактность).

Все строки юникода имеют метод закодировать encode(), используемый для представления символов юникода в качестве управляющих:

>>> u"дць".encode('utf-8') '\xc3\xa4\xc3\xb6\xc3\xbc'

Utf-8 означает тип кодировки юникода(Utf-8, Utf-16, ASCII).

Функция unicode() обеспечивает доступ ко всем кодерам / декодерам юникода: Utf-8, Utf-16, Latin-1, ASCII(0...127 символов), KOI-8, cp1251, cp866. Кодер / декодер по умолчанию – это ASCII, все вызовы функций str() и print используют кодер по умолчанию.

>>> unicode('\xc3\xa4\xc3\xb6\xc3\xbc', 'utf-8') u'\xe4\xf6\xfc'

Внимание:кодер по умолчанию ASCII не поддерживает кириллицу, поэтому в функции unicode надо указать предпочтительную кодировку(например, KOI-8).

>>> u"abc" u'abc'

>>> str(u"abc") 'abc'

>>> u"дць" u'\xe4\xf6\xfc'

>>> str(u"дць") ERROR: символ с кодом больше 127, не входит в ASCII.



Структуры.


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

class Employee: pass

john = Employee() # Создание пустой структуры

# Создаём и заполняем поля структуры john.name = 'Иван Иванович' john.dept = 'Программист' john.salary = 100000



Связывание выражений


Недовольный полурешениями, один из читателей - Ричарда Дейвис (Richard Davies) - поднял вопрос, можем ли мы целиком переместить связывания в отдельные выражения. Давайте попытаемся понять, зачем нам может этого захотеться, а также продемонстрируем замечательно элегантный способ этого добиться, предоставленный участником comp.lang.python.

Давайте сначала вспомним о классе Bindings, определенном в модуле functional. Используя свойства этого класса, мы смогли гарантировать, что отдельное имя имеет единственное значение в пределах области данного блока:

#------- Python FP session with guarded rebinding -------#

>>> from functional import *

      >>> let = Bindings()

      >>> let.car = lambda lst: lst[0]

      >>> let.car = lambda lst: lst[2]

      Traceback (innermost last):

        File "", line 1, in ?

        File "d:\tools\functional.py", line 976, in __setattr__

          raise BindingError, "Binding '%s' cannot be modified." % name

      functional.BindingError:  Binding 'car' cannot be modified.

      >>> let.car(range(10))

      0

С помощью класса Bindings нам удалось достичь желаемого результата в пределах модуля или функции, но в отношении отдельного выражения мы бессильны. Тем не менее, для семейства ML-языков вполне естественно создавать связывания в пределах отдельного выражения:

#-------- Haskell expression-level name bindings --------#

      -- car (x:xs) = x  -- *could* create module-level binding

      list_of_list = [[1,2,3],[4,5,6],[7,8,9]]

      -- 'where' clause for expression-level binding



      firsts1 = [car x | x <- list_of_list] where car (x:xs) = x

      -- 'let' clause for expression-level binding

      firsts2 = let car (x:xs) = x in [car x | x <- list_of_list]

      -- more idiomatic higher-order 'map' technique

      firsts3 = map car list_of_list where car (x:xs) = x

      -- Result: firsts1 == firsts2 == firsts3 == [1,4,7]

Грэг Эвинг (Greg Ewing) заметил, что мы можем достичь того же эффекта, воспользовавшись списочными встраиваниями Python  (list comprehensions); мы даже можем сделать это почти столь же ясным способом, как в Haskell:



#------ Python 2.0+ expression-level name bindings ------#

      >>> list_of_list = [[1,2,3],[4,5,6],[7,8,9]]

      >>> [car_x for x in list_of_list for car_x in (x[0],)]

      [1, 4, 7]

Этот прием - размещение выражения внутри одноэлементного кортежа в списочном встраивании - не позволяет использовать связывание на уровне выражений с функциями высшего порядка. Для использования таких функций мы все еще должны использовать область блока:



 #------- Python block-level bindings with 'map()' -------#

      >>> list_of_list = [[1,2,3],[4,5,6],[7,8,9]]

      >>> let = Bindings()

      >>> let.car = lambda l: l[0]

      >>> map(let.car,list_of_list)

      [1, 4, 7]

Неплохо, хотя если мы хотим использовать map(), область связывания остается несколько шире, чем мы того хотели. Тем не менее, можно уговорить списочное встраивание делать для нас связывание имен, даже если список - не то, что нам нужно в конечном счете:



      #---- "Stepping down" from Python list-comprehension ----#



      # Compare Haskell expression:

      # result = func car_car

      #          where

      #              car (x:xs) = x

      #              car_car = car (car list_of_list)

      #              func x = x + x^2

      >>> [func for x in list_of_list

      ...       for car in (x[0],)

      ...       for func in (car+car**2,)][0]

      2

В этом примере мы произвели арифметическое действие над первым элементом первого элемента списка list_of_list

и одновременно поименовали это действие (но только в области объемлющего выражения). В качестве "оптимизации" можно посоветовать создавать список длиной не более одного элемента, поскольку с помощью индекса [0] в конце выражения выбираем только первый элемент:



#---- Efficient stepping down from list-comprehension ---#

      >>> [func for x in list_of_list[:1]

      ...       for car in (x[0],)

      ...       for func in (car+car**2,)][0]       2


Время опроса


Давайте подведем итог, собрав вместе некоторые из интроспективных технологий, которые мы рассмотрели в последнем разделе. Для этого определим нашу собственную функцию interrogate(), которая выводит различную информацию о любом объекте, передаваемом в нее. Ниже приведен код, который сопровождается несколькими примерами его применения:



Листинг 35. Никто и не надеялся

>>> def interrogate(item): ... """Print useful information about item.""" ... if hasattr(item, '__name__'): ... print "NAME: ", item.__name__ ... if hasattr(item, '__class__'): ... print "CLASS: ", item.__class__.__name__ ... print "ID: ", id(item) ... print "TYPE: ", type(item) ... print "VALUE: ", repr(item) ... print "CALLABLE:", ... if callable(item): ... print "Yes" ... else: ... print "No" ... if hasattr(item, '__doc__'): ... doc = getattr(item, '__doc__') ... doc = doc.strip() # Remove leading/trailing whitespace. ... firstline = doc.split('\n')[0] ... print "DOC: ", firstline ... >>> interrogate('a string') # Строка CLASS: str ID: 141462040 TYPE: <type 'str'> VALUE: 'a string' CALLABLE: No DOC: str(object) -> string >>> interrogate(42) # Целое CLASS: int ID: 135447416 TYPE: <type 'int'> VALUE: 42 CALLABLE: No DOC: int(x[, base]) -> integer >>> interrogate(interrogate) # Функция, определенная пользователем NAME: interrogate CLASS: function ID: 141444892 TYPE: <type 'function'> VALUE: <function interrogate at 0x86e471c> CALLABLE: Yes DOC: Print useful information about item.

Как следует из последнего примера, наша функция interrogate() работает даже сама с собой. Большей ретроспективности вы вряд ли сможете получить.



Введение в язык Питон.


Питон – это объектно-ориентированный, интерпретируемый, переносимый язык сверхвысокого уровня. Программирование на Питоне позволяет получать быстро и качественно необходимые программные модули. Интерпретатор Питона может быть перенесён на любую платформу, будь то Unix, Windows, Linux, RiscOS, MAC, Sun. При написании кода на Питоне вы не должны заботиться о конечной платформе, кроме тех случаев, когда вы используете специфические модули для данной системы. Таким образом, Питон представляет серьёзную угрозу для Java, обеспечивая лёгкую переносимость, одновременно сочитая в себе средства доступа к ресурсам операционной системы. В отличие от Java Питон не столь строг к использованию объектов, но реализуются они столь просто, что любой программист легко понимает сущность объектно-ориентированного подхода. Кроме этого, модули Питона могут быть с лёгкостью использованы в ваших программах на С++ и, если вы знаете этот язык программирования, то освоение Питона будет для вас тривиально. Питон идеален в качестве cgi скриптов для веб-страниц, так как использует быстрый, эффективный и мощный интерпретатор. Питон может служить как в качестве языка для обучения, так и в качестве языка для разработки больших систем. Он прост, мощен и его очень легко освоить. Программы на Питоне можно написать в два раза быстрее, чем на Си, используя структуры высокого уровня, но по мощности он приближается к С++, избегая недостатков его безопасности и средств, черезмерно усложняющих язык(указатели).

Модули для Питона можно найти в сети Питона(). Модули Питона, как и сам интерпретатор свободны и доступны в виде исходных кодов или двоичных пакетах. В данном руководстве я постараюсь осветить основные аспекты Питона, как языка программирования, углублённое и полное руководство по языку может быть найдено на сайте (на английском языке). В данном руководстве я использовал версию Питона для О.С. Linux, пользователи других систем могут найти особенности своих систем на сайте

Запуск интерпретатора обычно осуществляется прямой командой python, или с указанием полного пути к интерпретатору.
Для того, чтобы выйти из Питона, воспользуйтесь комбинацией клавиш CTRL+D – Unix; CTRL+Z – Dos + Windows; если это не помогло набе­рите в ответ на приглашение интерпретатора(>>>) import sys; sys.exit(). Интерпретатор работает в двух режимах: интерактивном и собственно интерпретатора. Вход в интерактивный режим осуществляется вводом python без параметров, параметр file вызывает интерпретацию указанного файла. Для системы типа Unix есть альтернатива написания скриптов на Питоне – просто введите
#!/usr/local/bin/python #Some python script
Итак, вначале рассмотрим интерактивный режим: он напоминает shell. Вначале Питон пишет информацию о себе и о системе, а затем выводит своё приглашение(>>>), с этого момента он будет интерпретировать всё, поступающее с клавиатуры. Заставить Питон интерпретировать введённую вами строку можно клавишей Enter. Комментарии в Питоне обозначаются предваряющим их символом # и продолжаются до конца строки(т.е в Питоне все комментарии являются однострочными), при этом не допускается использование пред символом # кавычек:
>>> a = “Это строка”#Это комментарий >>> b = “#Это уже НЕ комментарий”
Интерпретатор Питона очень прост в использовании, например, вы можете его использовать в качестве калькулятора:
>>> 2+2 4
>>> # Это комментарий ... 2+2 4
>>> 2+2 # и комментарий в одной строке с оператором 4
>>> (50-5*6)/4 5
>>> #Целочисленное деление использует ОКРУГЛЕНИЕ до ближайшего меньшего целого ... 7/3 2
>>> 7/-3 -3
Переменные в Питоне не требуют объявления и могут первоначально содержать любой тип данных, что упрощает программирование, но наносит существенный ущерб стилю(по моему мнению). То есть вначале вы можете присвоить переменной а целый тип, переменной b строчный тип, но впоследствие нельзя присвоить переменной a, например, переменную b, так как они будут иметь разный тип:
>>> width = 20 >>> height = 5*9 >>> width * height 900


>>>some_string = “Это очень хорошая строка”
>>>some_string
Это очень хорошая строка
>>>width = some_string
ERROR: не могу присвоить переменные разных типов
Вы также можете присваивать одно и то же значение нескольким переменным одновременно:
>>> x = y = z = 0 # Присвоим нуль переменным x,y,z >>> x 0
>>> y 0
>>> z 0
Питон полностью поддерживает операции чисел с точкой:
>>> 3 * 3.75 / 1.5 7.5
>>> 7.0 / 2 #Обратите внимание, число с точкой делим на целое и получаем результат с точкой 3.5
В Питоне предусмотрена встроенная поддержка комплексных чисел. Воображаемая часть числа имеет суффикс j или J(1j; 5J), комплексные числа, имеющие ненулевую действительную часть обозначаются как real+imaginj или используя функцию complex(real, imagin):
>>> 1j * 1J (-1+0j)
>>> 1j * complex(0,1) (-1+0j)
>>> 3+1j*3 (3+3j)
>>> (3+1j)*3 (9+3j)
>>> (1+2j)/(1+1j) (1.5+0.5j)
Части комплексных чисел представляются, как числа с точкой, чтобы разделить комплексное число z на части, воспользуйтесь конструкцией z.real и z.imag:
>>> a=1.5+0.5j >>> a.real 1.5
>>> a.imag 0.5
Для преобразования одних численных типов в другие удобно пользоваться функциями
float() - приведение к числу с точкой
int() - приведение к целому числу
long() - приведение к длинному целому числу
Внимание: эти функции не работают для комплексных чисел, для них используйте разбиение на части или вычисление длины вектора функцией abs(z):
>>> a=3.0+4.0j >>> float(a) ERROR: не могу привести тип complex к типу float >>> a.real 3.0
>>> a.imag 4.0
>>> abs(a) # sqrt(a.real**2 + a.imag**2) 5.0
В интерактивном режиме последнее выведенное на экран выражение сохраняется в переменной по умолчанию _. Это удобно для продолжения вычислений в выражениях:
>>> tax = 12.5 / 100 >>> price = 100.50 >>> price * tax 12.5625
>>> price + _ 113.0625
>>> round(_, 2) 113.06
Однако не забудьте, что в эту переменную нельзя ничего непосредственно записать, т.к. это создаст дубликат переменной _, которая будет использоваться как обычная переменная.

Введение в объектно-ориентированное программирование.


Мы живём в мире объектов. Нас окружают различные предметы, они имеют определённые свойства: цвет, объём, вкус. Предметы могут выполнять различные действия, например, мячик может прыгать по столу. Объекты между собой взаимосвязаны, например, мячик взаимодействует со столом, когда прыгает по нему. Мы сразу же отличаем футбольный, волейбольный, баскетбольный и детский надувной мячи, но всех их мы называем общим понятием “мяч”, представляя у себя в уме некий образ мяча (идеальный шар), хотя реальный предмет может быть совсем не похожим на тот идеальный образ. Это пример наследования в реальном мире. Мы переходим от образа ко всё более конкретным объектам: образ – материальный предмет – фигура – шар – мяч – волейбольный мяч – профессиональный волейбольный мяч – мяч Николая Синего. При этом не исключается потомки у всех элементов цепи, то есть соблюдается принцип ветвления. Некоторые свойства мяча могут совершаться по-разному, что иллюстрирует принцип полиморфизма (своеобразная эволюция объектов). При этом мы не можем создать идеальный шар (поверхность любого “реального” шара не совсем ровная), есть некоторые понятия, которые являются абстрактными, их нельзя воссоздать в реальной жизни по причине ширины охвата других понятий(очень общее понятие), но можно создать другие, объекты, произошедшие от данного, но являющиеся более конкретными. В программировании такой подход в настоящее время получил наиболее широкое распространение. Ведь человек (а программисты в большинстве своём люди) проще понимают то, что видят, но это, как говорил Ходжа Насреддин:“Тонкий философский вопрос”. То есть человек понимает только тот подход, к которому он привык в реальной жизни. При создании серьёзных программных проектов объектный подход является единственным приемлемым, так как упрощает понимание общей структуры программы. Объектный подход, как и разбиение программы на модули(структурный подход) стремятся обеспечить сокрытие информации, дабы не копаться в коде программы, чтобы определить, что она делает, а взглянуть на определения функций и понять как они работают.
Нам ведь часто не интересно, как там общается программа с прерываниями центрального процессора, мы просто вызываем функцию считывания значения из консоли. При создании классов(именно так называются объекты в программировании) мы избавляем конечного пользователя от возни с разбором кода, а показываем ему структурную схему классов. Естественно в небольших программах выгоды от использования классов нет никакой(недостаток языка Java), но в крупных проектах они необходимы, как воздух.
Итак, вернёмся с небес на землю, то есть к Питону. В языке Питон использование классов является необязательным, но они реализованы по такому же принципу, как классы Си++(не все системные типы являются классами, в отличие от языка Java). В Питоне присутствует полная поддержка классов. Но в Питоне объекты класса создаются и уничтожаются автоматически, то есть вам не нужно для этой цели создавать особые методы. Внутри класса могут переопределяться любые стандартные операторы. Но обо всём по порядку.

Введение в списки.


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

>>> a = ['Да', 'Нет', 100, 1234] #Список состоит из разных типов >>> a ['Да', 'Нет', 100, 1234]

К отдельным элементам списка можно обращаться по индексу, аналогично индексам в строках:

>>> a[0] 'Да'

>>> a[3] 1234

>>> a[-2] 100

>>> a[1:-1] ['Нет', 100]

>>> a[:2] + ['Ой', 2*2] ['Да', 'Нет', 'Ой', 4]

>>> 3*a[:3] + ['Ай!'] ['Да', 'Нет', 100, 'Да', 'Нет', 100, 'Да', 'Нет', 100, 'Ай!']

В отличие от строк в Питоне вы можете изменять отдельные элементы списка по индексу(при этом учтите: чтобы удалить элемент из списка надо присвоить ему пустой список[]):

>>> a ['Да', 'Нет', 100, 1234]

>>> a[2] = a[2] + 23#Увеличиваем 100 на 23
>>> a ['Да', 'Нет', 123, 1234]

Возможно также присваивание частей списков частям другого списка ил целому списку. Возможно склеивание частей списков:

>>> # Заменим некоторые элементы ... a[0:2] = [1, 12] >>> a [1, 12, 123, 1234]

>>> # Удалим некоторые элементы ... a[0:2] = [] >>> a [123, 1234]

>>> # Теперь вставим ... a[1:1] = ['ёпрст', 'абвгд'] >>> a [123, 'ёпрст', 'абвгд', 1234]

>>> a[:0] = a # Вставим копию самого списка в начало списка >>> a [123, 'ёпрст', 'абвгд', 1234, 123, 'ёпрст', 'абвгд', 1234]

Функция len() позволяет определить длину списка(т.е. количество элементов в нём):

>>> len(a) 8

Возможно создание вложенных списков, т.е. списков, содержащих в качестве элементов другие списки:

>>> q = [2, 3]
>>> p = [1, q, 4] >>> len(p) 3

>>> p[1] [2, 3]

>>> p[1][0] 2

>>> p[1].append('вставление') # См далее(вставляет элемент в конец списка) >>> p [1, [2, 3, 'вставление'], 4]

>>> q [2, 3, 'вставление']

Внимание: p[1] и q ссылаются на один и тот же объект(к этому я вернусь далее).



Выражения в списках.


В Питоне есть альтернативный способ создания списков по определённым правилам, позволяющий избегать использования функций filter(), map(), reduce(): использование выражений внутри списков. Такие выражения имеют следующий формат: заголовок цикла for, задающий ограничения при создании списков, за этим циклом может(необязательно) следовать некоторое количество условий if и циклов for, по которым, собственно, и создаётся результативный список. Приведём пример таких выражений:

>>> freshfruit = [' банан', ' ананас', 'яблоко '] >>> [weapon.strip() for weapon in freshfruit] ['банан', 'ананас', 'яблоко']

>>> vec = [2, 4, 6] >>> [3*x for x in vec] [6, 12, 18]

>>> [3*x for x in vec if x > 3] [12, 18]

>>> [3*x for x in vec if x < 2] []

>>> [{x: x**2} for x in vec] [{2: 4}, {4: 16}, {6: 36}]

>>> [[x,x**2] for x in vec] [[2, 4], [4, 16], [6, 36]]

>>> [(x, x**2) for x in vec] [(2, 4), (4, 16), (6, 36)]

>>> vec1 = [2, 4, 6] >>> vec2 = [4, 3, -9] >>> [x*y for x in vec1 for y in vec2] [8, 6, -18, 16, 12, -36, 24, 18, -54]

>>> [x+y for x in vec1 for y in vec2] [6, 5, -7, 8, 7, -5, 10, 9, -3]

>>> [vec1[i]*vec2[i] for i in range(len(vec1))] [8, 12, -54]



Вызываемые структуры


Объекты, которые представляют потенциальное поведение (функции и методы), могут быть запущены, или вызваны. С помощью функции callable() мы можем установить, вызываемый ли это объект:



Листинг 32. Можешь что-нибудь для меня сделать?

>>> print callable.__doc__ callable(object) -> Boolean

Return whether the object is callable (i.e., some kind of function). Note that classes are callable, as are instances with a __call__() method.

>>> callable('a string') 0 >>> callable(dir) 1



Эта статья продемонстрировала способы замены


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

представляемые как отложенные последовательности, мощная


Итераторы, представляемые как отложенные последовательности, мощная концепция, открывающая новые стили Питоновского программирования. Хотя имеется тонкое различие между представлением итератора как источника данных и его представлением в последовательном стиле. Ни один способ представления сам по себе не правильнее другого, но второй открывает путь к комбинаторному сокращению для манипулирования программными событиями. Комбинаторные функции в itertools (и особенно некоторые, которые он может развить, как те, что я предлагаю) близко подходят к декларативному стилю программирования. По моему мнению, эти декларативные стили менее подвержены ошибкам и более мощны.

Кто бы мог подумать, что


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


Заключительные действия.


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

>>> try: ... raise KeyboardInterrupt ... finally: ... print 'Goodbye, world!' ... Goodbye, world!

Traceback (most recent call last):

File "<stdin>", line 2, in ?

KeyboardInterrupt

__finally блок выполняется независимо от того произошло исключение или нет, если исключение произошло, то оно возбуждается ещё раз. Оператор try может иметь или блоки except или __finally, но не одновременно. Блок __finally выполняется даже во вложенных try, поэтому использование вложенных try(с except) и внешнего с __finally способно отловить и корректно обработать любые возникшие исключения.



Закрытые переменные.


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

Если вы объявляете какой-либо элемент, начиная его с двойного подчёркивания, то он автоматически становится закрытым и обращение к нему вне класса вызывает синтаксическую ошибку, в то время как обращение через self является приемлемым:

>>> class Test2: ... __foo = 0 ... def set_foo(self, n): ... if n > 1: ... self.__foo = n ... print self.__foo ... >>> x = Test2() >>> x.set_foo(5)

5

>>> x.__foo Traceback (most recent call last): File "<interactive input>", line 1, in ? AttributeError: Test2 instance has no attribute '__foo'

Кроме этого закрытой является также переменная, содержащаяся в любом модуле __dict__.



Замыкание


В ФП существует очень интересное понятие - замыкание (closure). На самом деле, эта идея оказалась настолько заманчивой для многих разработчиков, что реализована даже в нефункциональных языках программирования, таких как Perl и Ruby. Кроме того, похоже, что в Python 2.1 неизбежно будет включен лексический контекст, что на 99% приблизит нас к замыканиям.

Так что же такое замыкание? Стив Маджевски (Steve Majewski) замечательно охарактеризовал это понятие в одной из посвященных Python сетевых конференций:

Объект - это совокупность данных вместе с привязанными к ним процедурами...

Замыкание - это процедура вместе с привязанноой к ней совокупностью данных.

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

Давайте вернемся немного назад, чтобы понять, какую проблему решают как объекты, так и замыкания, и как эта проблема решается без них. Обычно результат, возвращаемый функцией, определяется контекстом во время ее вычисления. Самый общий - и, возможно, самый очевидный - способ определить этот контекст - это передать в функцию параметры, указывающие, какие значения должны обрабатываться. Но иногда имеется вполне очевидное различие между "фоновыми" и "приоритетными" параметрами - между тем, что делает функция в данный момент, и тем, как она сконфигурирована для выполнения многократных потенциальных вызовов.

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




#--------- Python session showing cargo variable --------#

>>> def a(n):

...     add7 = b(n)

...     return add7

...

>>> def b(n):

...     i = 7

...     j = c(i,n)

...     return j

...

>>> def c(i,n):

...     return i+n

...

>>> a(10)     # Pass cargo value for use downstream

17

В этом примере, параметр n в пределах функции b() нужен только для того, чтобы быть доступным для передачи в c().

Другое возможное решение - использование глобальных переменных:



#--- Сессия Python, показывающая использование глобальной переменной ---#

>>> N = 10

>>> def addN(i):

...     global N

...     return i+N

...

>>> addN(7)   # Добавить глобальную переменную N к аргументу

17

>>> N = 20

>>> addN(6)   # Добавить глобальную переменную N к аргументу

26

Глобальная переменная доступна в любой момент, где бы вы ни вызывали addN(), при этом вовсе не обязательно явно передавать фоновый контекст .

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



#-------- Сессия Python, иллюстрирующая замороженную переменную --------#

>>> N = 10

>>> def addN(i, n=N):

...     return i+n

...

>>> addN(5)   # Добавить 10

15

>>> N = 20

>>> addN(6)   # Добавить 10 (текущее значение N не играет роли)

16

Замороженная нами переменная, в сущности, замыкание. Некие данные прикреплены к функции addN(). В случае полного

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


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

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



#----- Класс в стиле Python для вычисления налога ------#

       class TaxCalc:

           def taxdue(self):

               return (self.income-self.deduct)*self.rate

       taxclass = TaxCalc()

       taxclass.income = 50000

       taxclass.rate = 0.30

       taxclass.deduct = 10000

       print "Pythonic OOP taxes due =", taxclass.taxdue()

В нашем классе TaxCals (точнее, в его экземпляре) мы можем собрать данные - в любом порядке - и как только у нас будут все необходимые элементы, можно будет вызвать метод объекта, чтобы выполнить вычисление над собранными данными. Все собрано в пределах экземпляра и, тем самым, разные экземпляры могут задавать разные наборы данных. Создание множества экземпляров, отличающихся друг от друга только своими данными, невозможно при использовании глобальных или "замороженных" переменных. Подход "грубой силы" может решить эту задачу, но можно видеть, что в реальном примере может понадобиться передача множества значений.

Теперь давайте посмотрим, как можно решить эту задачу с помощью ООП с использованием передачи сообщений (это похоже на Smalltalk или Self, так же как и на некоторые объектно-ориентированные варианты xBase, которыми я пользовался):





#------- Smalltalk-style (Python) tax calculation -------#

       class TaxCalc:

          def taxdue(self):

              return (self.income-self.deduct)*self.rate

          def setIncome(self,income):

              self.income = income

              return self

          def setDeduct(self,deduct):

              self.deduct = deduct

              return self

          def setRate(self,rate):

                    self.rate = rate

              return self

      print "Smalltalk-style taxes due =", \

            TaxCalc().setIncome(50000).setRate(0.30).setDeduct(10000).taxdue()

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

Работая с Xoltar toolkit, можно создавать полные замыкания, имеющие требуемое свойство объединения данные с функцией, а также множественные замыкания, несущие в себе различные наборы данных:



#------- Python Functional-Style tax calculations -------#

      from functional import *

      taxdue        = lambda: (income-deduct)*rate



      incomeClosure = lambda income,taxdue: closure(taxdue)

      deductClosure = lambda deduct,taxdue: closure(taxdue)

      rateClosure   = lambda rate,taxdue: closure(taxdue)

      taxFP = taxdue

      taxFP = incomeClosure(50000,taxFP)

      taxFP = rateClosure(0.30,taxFP)

      taxFP = deductClosure(10000,taxFP)

      print "Functional taxes due =",taxFP()

      print "Lisp-style taxes due =", \

            incomeClosure(50000,

                rateClosure(0.30,

                    deductClosure(10000, taxdue)))()

Каждая описанная нами функция замыкания берет любые значения, определенные в ее области видимости, и привязывает эти величины к глобальной области видимости функционального объекта. Однако то, что кажется глобальной областью данной функции, необязательно совпадает как с "настоящей" глобальной областью действия модуля, так и с глобальной областью другого замыкания. Замыкание просто "несет данные с собой".

В нашем примере, чтобы поместить определенные значения в область действия замыкания, мы используем несколько частных функций (income, deduct, rate). Было бы достаточно просто изменить дизайн так, чтобы было можно присваивать произвольные значения. Кроме того, ради развлечения, мы используем в этом примере два слегка различных функциональных стиля. Первый последовательно привязывает дополнительные значения к области замыкания; сделав taxFP изменяемой, мы позволяем строкам добавить в замыкание появляться в любом порядке. Однако, если бы мы использовали неизменяемые имена наподобие tax_with_Income, нам пришлось бы  расположить связывания в определенном порядке и передавать более ранние последующим.


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

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

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

Другая интересная деталь Lisp-стиля в том, насколько сильно его использование замыканий напоминает методы передачи сообщений a la Smalltalk, о которых говорилось выше. В обоих случаях значения накопливаются до вызова функции/метода taxdue()

(оба в этих упрощенных версиях возбуждают исключения, если требуемые данные недоступны). Smalltalk-стиль на каждом шаге передает объект, в то время как Lisp-стиль - продолжение (continuation). Но если смотреть в корень, то функциональное и объектно-ориентированное программирование приводят почти к одному и тому же.