Главная страница

Математический анализ. 3е издание


Скачать 4.86 Mb.
Название3е издание
АнкорМатематический анализ
Дата04.02.2022
Размер4.86 Mb.
Формат файлаpdf
Имя файлаpython_01.pdf
ТипДокументы
#351981
страница95 из 98
1   ...   90   91   92   93   94   95   96   97   98
Часть IV. Функции
Упражнения находятся в главе 17, в разделе «Упражнения к четвер
той части».
1. Основы. В этом упражнении нет ничего сложного, но обратите вни
мание на то, что инструкция print (а следовательно, и ваша функ
ция) с технической точки зрения является полиморфической опера
цией, которая правильно интерпретирует типы всех объектов:
% python
>>> def func(x): print x

798
Приложение B
>>> func("spam")
spam
>>> func(42)
42
>>> func([1, 2, 3])
[1, 2, 3]
>>> func({'food': 'spam'})
{'food': 'spam'}
2. Аргументы. Ниже приводится пример решения. Не забывайте, что для вывода результата необходимо использовать инструкцию print,
потому что выполнение программного кода в модуле – это совсем не то же самое, что выполнение программного кода, который вводится в интерактивной оболочке, – интерпретатор не выводит автомати
чески результаты выражений, вычисляемых в файлах модулей:
def adder(x, y):
return x + y print adder(2, 3)
print adder('spam', 'eggs')
print adder(['a', 'b'], ['c', 'd'])
% python mod.py
5
spameggs
['a', 'b', 'c', 'd']
3. Переменное число аргументов. В следующем файле adder.py пока
заны два варианта реализации функции adder. Самое сложное здесь заключается в инициализации значения суммы пустым значением в зависимости от типа передаваемых аргументов. Первое решение выясняет, является ли первый аргумент целым числом, и извлека
ет пустой срез первого аргумента (предполагается, что он является последовательностью), если он не является целым числом. Во вто
ром решении в качестве начального значения используется первый аргумент, а затем выполняется сканирование аргументов, начиная со второго, почти как в одном из вариантов функции min, показан
ной в главе 16.
Второе решение является более предпочтительным. Но в обоих ва
риантах предполагается, что все аргументы принадлежат к одному и тому же типу, а, кроме того, предполагается, что функция не бу
дет работать со словарями (как мы видели во второй части книги,
оператор + не работает с операндами разных типов и со словарями).
Вы можете добавить проверку типа аргументов и предусмотреть специальный программный код для обработки словарей, за что вам причитаются дополнительные баллы:
def adder1(*args):
print 'adder1',
if type(args[0]) == type(0): # Целое число?

Решения упражнений
799
sum = 0 # Инициализировать нулем
else: # иначе  последовательность:
sum = args[0][:0] # Использовать пустой срез первого аргумента
for arg in args:
sum = sum + arg return sum def adder2(*args):
print 'adder2',
sum = args[0] # Инициализировать значением первого аргумента
for next in args[1:]:
sum += next # Прибавить аргументы 2..N
return sum for func in (adder1, adder2):
print func(2, 3, 4)
print func('spam', 'eggs', 'toast')
print func(['a', 'b'], ['c', 'd'], ['e', 'f'])
% python adders.py
adder1 9
adder1 spameggstoast adder1 ['a', 'b', 'c', 'd', 'e', 'f']
adder2 9
adder2 spameggstoast adder2 ['a', 'b', 'c', 'd', 'e', 'f']
4. Передача аргументов по ключу. Ниже приводится мое решение первой части этого упражнения (файл mod.py). Для обхода аргумен
тов, передаваемых по ключу, в заголовке функции следует исполь
зовать форму списка аргументов **args и цикл в теле функции (на
пример, for x in args.keys(): и использовать args[x]) или использо
вать args.values(), чтобы реализовать сложение по позициям аргу
ментов в списке *args:
def adder(good=1, bad=2, ugly=3):
return good + bad + ugly print adder()
print adder(5)
print adder(5, 6)
print adder(5, 6, 7)
print adder(ugly=7, good=6, bad=5)
% python mod.py
6 10 14 18 18 5. (и 6.) Ниже приводится мое решение упражнений 5 и 6 (файл
dicts.py
). Однако это решение пригодно только в качестве упражне
ния, потому что в Python 1.5 у словарей появились методы D.copy()

800
Приложение B
и D1.update(D2), которые реализуют операции копирования и сложе
ние (слияние) словарей. (За дополнительной информацией обращай
тесь к справочному руководству по библиотеке Python или к книге
«Python Pocket Reference», выпущенной издательством O’Reilly.)
Операция извлечения среза X[:] не применима к словарям, потому что они не являются последовательностями (подробности в главе 8).
Кроме того, не забывайте, что в случае присваивания (e = d) просто создается вторая ссылка на один и тот же объект словаря – измене
ния в словаре d будут приводить к изменениям в словаре e:
def copyDict(old):
new = {}
for key in old.keys():
new[key] = old[key]
return new def addDict(d1, d2):
new = {}
for key in d1.keys():
new[key] = d1[key]
for key in d2.keys():
new[key] = d2[key]
return new
% python
>>> from dicts import *
>>> d = {1: 1, 2: 2}
>>> e = copyDict(d)
>>> d[2] = '?'
>>> d
{1: 1, 2: '?'}
>>> e
{1: 1, 2: 2}
>>> x = {1: 1}
>>> y = {2: 2}
>>> z = addDict(x, y)
>>> z
{1: 1, 2: 2}
6. Дополнительные примеры на сопоставление аргументов. Ниже приводится пример диалога с интерактивной оболочкой, который вы должны получить в ходе выполнения этого упражнения, с неко
торыми комментариями, поясняющими порядок сопоставления ар
гументов:
def f1(a, b): print a, b # Обычные аргументы
def f2(a, *b): print a, b # Сопоставление выполняется по позиции
def f3(a, **b): print a, b # Сопоставление выполняется по ключам
def f4(a, *b, **c): print a, b, c # Смешанный режим

Решения упражнений
801
def f5(a, b=2, c=3): print a, b, c # Аргументы со значениями по умолчанию
def f6(a, b=2, *c): print a, b, c # Аргументы по умолчанию и позиционные
% python
>>> f1(1, 2) # Сопоставление по позиции (важен порядок)
1 2
>>> f1(b=2, a=1) # Сопоставление по имени (порядок не важен)
1 2
>>> f2(1, 2, 3) # Дополнительные аргументы объединяются
1 (2, 3) # в кортеж
>>> f3(1, x=2, y=3) # Дополнительные ключи объединяются
1 {'x': 2, 'y': 3} # в словарь
>>> f4(1, 2, 3, x=2, y=3) # Дополнительные аргументы обоих типов
1 (2, 3) {'x': 2, 'y': 3}
>>> f5(1) # Два последних аргумента получают
1 2 3 # значения по умолчанию
>>> f5(1, 4) # Используется только одно значение
1 4 3 # по умолчанию
>>> f6(1) # Один аргумент: соответствует имени "a"
1 2 ()
>>> f6(1, 3, 4) # Дополнительный позиционный аргумент
1 3 (4,) # собирается в кортеж
7. Снова простые числа. Ниже приводится пример нахождения про
стых чисел, оформленный в виде функции в модуле (файл primes.py),
которую можно вызывать несколько раз. Я добавил проверку, чтобы отсечь отрицательные числа, 0 и 1. Кроме того, я заменил оператор /
на //, чтобы это решение правильно работало в Python 3.0, где будут внесены изменения в действие оператора /, о чем рассказывалось в главе 5, и чтобы добавить поддержку чисел с плавающей точкой.
В настоящее время в решении можно использовать как оператор //,
так и оператор /, но в будущем оператор / будет завершаться ошиб
кой (раскомментируйте инструкцию from и замените оператор // на /
, чтобы увидеть разницу между версиями Python 2.2 и 3.0):
#from __future__ import division def prime(y):
if y <= 1: # У некоторых может быть y > 1
print y, 'not prime'
else:
x = y // 2 # В будущем / будет терпеть неудачу
while x > 1:
if y % x == 0: # нет остатка?
print y, 'has factor', x break # Обойти ветку else
x = 1

802
Приложение B
else:
print y, 'is prime'
prime(13); prime(13.0)
prime(15); prime(15.0)
prime(3); prime(2)
prime(1); prime(3)
Далее приводится пример работы модуля. Оператор // позволяет модулю работать с вещественными числами тоже, хотя этого быть не должно.
% python primes.py
13 is prime
13.0 is prime
15 has factor 5 15.0 has factor 5.0 3 is prime
2 is prime
1 not prime
3 not prime
Эта функция все еще не слишком пригодна к многократному ис
пользованию – вместо вывода результата она могла бы возвращать значения, но для экспериментов вполне достаточно и такой реали
зации. Кроме того, она не строго соответствует математическому определению простых чисел, которыми могут быть только целые числа, и не обладает достаточной эффективностью. Эти улучшения я оставляю для самостоятельной реализации читателям, склонным к математике. Подсказка: цикл for через range(y, 1, 1) будет вы
полняться немного быстрее, чем цикл while (на самом деле, в Py
thon 2.2 скорость увеличивается почти в два раза), но самым узким местом здесь является сам алгоритм. Для измерения производи
тельности альтернативных реализаций воспользуйтесь встроенным модулем time и отрывком кода, подобным следующей универсаль
ной функции timer (за дополнительной информацией обращайтесь к справочному руководству по библиотеке):
def timer(reps, func, *args):
import time start = time.clock()
for i in xrange(reps):
apply(func, args)
return time.clock()  start
8. Генераторы списков. Ниже приводится вариант программного ко
да, который должен получиться у вас, – у меня есть свои предпочте
ния, но я о них умолчу:
>>> values = [2, 4, 9, 16, 25]
>>> import math
>>> res = []

Решения упражнений
803
>>> for x in values: res.append(math.sqrt(x))
>>> res
[1.4142135623730951, 2.0, 3.0, 4.0, 5.0]
>>> map(math.sqrt, values)
[1.4142135623730951, 2.0, 3.0, 4.0, 5.0]
>>> [math.sqrt(x) for x in values]
[1.4142135623730951, 2.0, 3.0, 4.0, 5.0]
Часть V. Модули
Упражнения находятся в главе 21, в разделе «Упражнения к пятой части».
1. Основы импортирования. Решение этого упражнения выглядит го
раздо проще, чем можно было бы подумать. Когда вы закончите,
у вас должен получиться файл (mymod.py) и сеанс взаимодействия с интерактивной оболочкой, как показано ниже. Не забывайте, что интерпретатор Python moжет читать содержимое файла целиком в список строк, а получить длину каждой строки в списке можно с помощью встроенной функции len:
def countLines(name):
file = open(name)
return len(file.readlines())
def countChars(name):
return len(open(name).read())
def test(name): # Или передать объект файла
return countLines(name), countChars(name) # Или вернуть словарь
% python
>>> import mymod
>>> mymod.test('mymod.py')
(10, 291)
Обратите внимание, что эти функции читают файл в память цели
ком и потому не в состоянии работать с патологически большими файлами, которые не смогут уместиться в памяти компьютера. Что
бы обеспечить более высокую устойчивость, можно организовать построчное чтение содержимого файла с помощью итератора и на
капливать длины строк в ходе итераций:
def countLines(name):
tot = 0
for line in open(name): tot += 1
return tot def countChars(name):
tot = 0

804
Приложение B
for line in open(name): tot += len(line)
return tot
В операционной системе UNIX полученный результат можно про
верить с помощью команды wc. В Windows можно щелкнуть на фай
ле правой кнопкой мыши и посмотреть его свойства. Обратите вни
мание, что результат, возвращаемый сценарием, может несколько отличаться от того, что сообщает Windows, – с целью обеспечения переносимости интерпретатор Python преобразует пары символов
\r\n
, отмечающих конец каждой строки, в один символ \n, в резуль
тате чего теряется по одному байту на каждую строку. Чтобы ре
зультат сценария в точности соответствовал тому, что сообщает
Windows, файл необходимо открыть в режиме двоичного доступа
('rb') или прибавлять к общему результату число строк.
Чтобы реализовать часть упражнения «для честолюбивых» (пере
давать функциям объект файла, чтобы открывать его приходилось всего один раз), вам, скорее всего, придется использовать метод seek объекта файла. Мы не рассматривали этот метод в книге, но он ра
ботает точно так же, как функция fseek в языке C (которая, собст
венно, и вызывается внутренней реализацией метода): метод seek переустанавливает текущую позицию в файле в указанное смеще
ние. После вызова метода seek последующие операции ввода/выво
да будут выполняться относительно новой позиции. Чтобы пере
меститься в начало файла, не закрывая и не открывая его повторно,
можно вызвать метод file.seek(0); все вызовы метода read выполня
ют чтение из текущей позиции в файле, поэтому, чтобы начать по
вторное чтение, необходимо переместить текущую позицию в нача
ло файла. Ниже показано, как может выглядеть такая реализация:
def countLines(file):
file.seek(0) # Переместиться в начало файла
return len(file.readlines())
def countChars(file):
file.seek(0) # То же самое(переместиться в начало)
return len(file.read())
def test(name):
file = open(name) # Передать объект файла
return countLines(file), countChars(file) # Открыть файл один раз
>>> import mymod2
>>> mymod2.test("mymod2.py")
(11, 392)
2. from/from *. Ниже приводится решение в части использования инст
рукции вида from *. Чтобы выполнить вторую часть упражнения,
замените * именем countChars:
% python
>>> from mymod import *

Решения упражнений
805
>>> countChars("mymod.py")
291 3. __main__. Если вы не допустили ошибок в модуле, он сможет рабо
тать в любом из режимов (в режиме самостоятельной программы или в режиме импортируемого модуля):
def countLines(name):
file = open(name)
return len(file.readlines())
def countChars(name):
return len(open(name).read())
def test(name): # Или передать объект файла
return countLines(name), countChars(name) # Или вернуть словарь
if __name__ == '__main__':
print test('mymod.py')
% python mymod.py
(13, 346)
4. Вложенное импортирование. Ниже представлено мое решение
(файл myclient.py):
from mymod import countLines, countChars print countLines('mymod.py'), countChars('mymod.py')
% python myclient.py
13 346
Что касается остальной части этого упражнения, функции модуля mymod доступны (то есть были импортированы) на верхнем уровне мо
дуля myclient, потому что инструкция from просто присваивает их именам в импортирующем модуле (выглядит так, как если бы опре
деления функций def находились в модуле myclient). Например, в сле
дующем файле приводится альтернативный вариант реализации:
import myclient myclient.countLines(...)
from myclient import countChars countChars(...)
Если бы в модуле myclient вместо инструкции from использовалась инструкция import, вам пришлось бы использовать полные имена функций из модуля mymod:
import myclient myclient.mymod.countLines(...)
from myclient import mymod mymod.countChars(...)
Вообще говоря, можно определить модуль+коллектор, который будет импортировать все имена из других модулей, чтобы сделать их дос

806
Приложение B
тупными в виде единого модуля. Если воспользоваться следующим программным кодом, вы покончите с тремя разными копиями имени somename
(mod1.somename, collector.somename и __main__.somename) – все три имени изначально будут ссылаться на один и тот же объект це
лого числа, а в интерактивной оболочке будет существовать только одно имя somename:
# Файл: mod1.py somename = 42
# Файл: collector.py from mod1 import * # Выборка всех имен переменных from mod2 import * # из инструкций присваивания и превращение их from mod3 import * # в собственные имена этого модуля
>>> from collector import somename
5. Импорт пакетов. Для этого упражнения я поместил файл mymod.py
из упражнения 3 в каталог пакета. Далее описываются мои действия по настройке каталога и необходимого файла __init__.py в консоли
Windows – вы должны учитывать особенности своей платформы
(например, использовать команды mv и vi вместо move и edit). Эти действия можно выполнять в любом произвольном каталоге (про
сто так вышло, что я запускал команды, находясь в каталоге, куда был установлен Python), частично эти действия можно выполнить в проводнике файловой системы с графическим интерфейсом.
По окончании я получил подкаталог mypkg, содержащий файлы
__init__.py
и mymod.py. В каталоге mypkg обязательно должен нахо
диться файл __init__.py, но его наличие в родительском каталоге не
обязательно. Каталог mypkg находится в домашнем каталоге, в пути поиска модулей. Обратите внимание, что инструкция print в ини
циализационном файле выполняется только при первой операции импорта:
C:\python25> mkdir mypkg
C:\Python25> move mymod.py mypkg\mymod.py
C:\Python25> edit mypkg\__init__.py
...добавление инструкции print...
C:\Python25> python
>>> import mypkg.mymod
initializing mypkg
>>> mypkg.mymod.countLines('mypkg\mymod.py')
13
>>> from mypkg.mymod import countChars
>>> countChars('mypkg\mymod.py')
346 6. Повторная загрузка. В этом упражнении вам просто предлагает поэкспериментировать с возможностью повторной загрузки модуля
changer.py
из примера в книге, поэтому мне нечего показать здесь.

Решения упражнений
807
7. Циклический импорт. Суть в том, что, когда первым импортируется модуль recur2, ситуация рекурсивного импорта возникает в инструк
ции import в модуле recur1, а не в инструкции from в модуле recur2.
Если говорить более подробно, все происходит следующим образом:
когда модуль recur2 импортируется первым, в результате рекурсив
ного импорта модуля recur2 из модуля recur1 модуль recur2 извлека
ется целиком. К моменту, когда модуль recur2 импортируется моду
лем recur1, его пространство имен еще не заполнено, но так как им
портирование выполняется инструкцией import, а не инструкцией from
, никаких проблем не возникает: интерпретатор отыскивает и возвращает уже созданный объект модуля recur2 и продолжает выполнять оставшуюся часть модуля recur1 без сбоев. Когда импорт в модуле recur2 возобновляется, вторая инструкция from обнаружи
вает в модуле recur1 имя Y (модуль был выполнен полностью), по
этому в такой ситуации не возникает ошибок. Запуск файла как от
дельного сценария – это не то же самое, что импортирование его в виде модуля: это равноценно интерактивному выполнению первой инструкции import или from в сценарии. Например, запуск модуля recur1
как сценария равносилен предварительному импортирова
нию модуля recur2 в интерактивной оболочке, поскольку recur2 –
это первый модуль, импортируемый в recur1.
Часть VI. Классы и ООП
Упражнения находятся в главе 26, в разделе «Упражнения к шестой части».
1. Наследование. Ниже приводится решение этого упражнения (файл
adder.py
) вместе с несколькими примерами действий в интерактив
ной оболочке. Метод перегрузки оператора __add__ присутствует только в суперклассе и вызывает конкретные методы add, опреде
ляемые подклассами:
class Adder:
def add(self, x, y):
print 'not implemented!'
def __init__(self, start=[]):
self.data = start def __add__(self, other): # Или в подклассах?
return self.add(self.data, other) # Или возвращать тип?
class ListAdder(Adder):
def add(self, x, y):
return x + y class DictAdder(Adder):
def add(self, x, y):
new = {}
for k in x.keys(): new[k] = x[k]
for k in y.keys(): new[k] = y[k]

808
Приложение B
return new
% python
>>> from adder import *
>>> x = Adder()
>>> x.add(1, 2)
not implemented!
>>> x = ListAdder()
>>> x.add([1], [2])
[1, 2]
>>> x = DictAdder()
>>> x.add({1:1}, {2:2})
{1: 1, 2: 2}
>>> x = Adder([1])
>>> x + [2]
not implemented!
>>>
>>> x = ListAdder([1])
>>> x + [2]
[1, 2]
>>> [2] + x
Traceback (innermost last):
File "", line 1, in ?
TypeError: __add__ nor __radd__ defined for these operands
(TypeError: ни __add__, ни __radd__ не определен для этих операндов)
Обратите внимание, что в последнем тесте возникла ошибка при по
пытке использовать экземпляр класса справа от оператора +. Если вы захотите исправить эту ошибку, реализуйте метод __radd__, как описывается в главе 24, в разделе «Перегрузка операторов».
Если вы начнете сохранять текущее значение в экземпляре, вы мо
жете также переписать метод add, чтобы он принимал единствен
ный аргумент, в духе других примеров из шестой части:
class Adder:
def __init__(self, start=[]):
self.data = start def __add__(self, other): # Передается единственный аргумент
return self.add(other) # Операнд слева хранится в self
def add(self, y):
print 'not implemented!'
class ListAdder(Adder):
def add(self, y):
return self.data + y class DictAdder(Adder):
def add(self, y):
pass # Измените, чтобы использовать self.data вместо x
x = ListAdder([1, 2 ,3])
y = x + [4, 5, 6]
print y # Выведет [1, 2, 3, 4, 5, 6]

Решения упражнений
809
Поскольку значения присоединяются к самим объектам, а не пере
даются в виде аргументов, эта версия определенно является более объектноориентированной. И как только вы доберетесь до этого мо
мента, вы наверняка обнаружите, что вообще можно избавиться от методов add и просто определить методы __add__ в двух подклассах.
2. Перегрузка операторов. В программном коде решения (файл myl+
ist.py
) используется несколько методов перегрузки операторов, о ко
торых в книге рассказывается не слишком много, однако в них нет ничего сложного. Операция копирования начального значения в конструкторе имеет большое значение, потому что оно может быть представлено изменяемым объектом – едва ли есть разумная причи
на, чтобы изменять или обладать ссылкой на объект, который,
вполне возможно, используется гдето за пределами класса. Метод
__getattr__
перехватывает попытки обращения к обернутому спи
ску. Подсказки, которые помогут упростить реализацию, вы найде
те в разделе «Расширение типов наследованием» в главе 26:
class MyList:
def __init__(self, start):
#self.wrapped = start[:] # Скопировать start:
# без побочных эффектов
self.wrapped = [] # Убедиться, что это список
for x in start: self.wrapped.append(x)
def __add__(self, other):
return MyList(self.wrapped + other)
def __mul__(self, time):
return MyList(self.wrapped * time)
def __getitem__(self, offset):
return self.wrapped[offset]
def __len__(self):
return len(self.wrapped)
def __getslice__(self, low, high):
return MyList(self.wrapped[low:high])
def append(self, node):
self.wrapped.append(node)
def __getattr__(self, name): # Другие члены: sort/reverse/и т. д.
return getattr(self.wrapped, name)
def __repr__(self):
return repr(self.wrapped)
if __name__ == '__main__':
x = MyList('spam')
print x print x[2]
print x[1:]
print x + ['eggs']
print x * 3
x.append('a')
x.sort()
for c in x: print c,

810
Приложение B
% python mylist.py
['s', 'p', 'a', 'm']
a
['p', 'a', 'm']
['s', 'p', 'a', 'm', 'eggs']
['s', 'p', 'a', 'm', 's', 'p', 'a', 'm', 's', 'p', 'a', 'm']
a a m p s
Обратите внимание, насколько важно копировать начальное значе
ние, выполняя добавление в конец вместо извлечения среза, потому что в противном случае может получиться объект, не являющийся списком и потому не обладающий методами списка, такими как ap
pend
(например, операция извлечения среза строки возвращает дру
гую строку, но не список). Если бы начальное значение было объек
том типа MyList, можно было бы выполнить копирование с помощью операции извлечения среза, потому что этот класс перегружает эту операцию и обеспечивает ожидаемый интерфейс списков. Однако для других объектов, таких как строки, следует избегать использо
вания операции извлечения среза. Кроме того, следует заметить,
что на сегодняшний день тип множества стал встроенным типом в языке Python, поэтому цель данного упражнения в значительной степени заключается в том, чтобы просто дать возможность попрак
тиковаться (подробнее о множествах рассказывается в главе 5).
3. Подклассы. Мое решение (файл mysub.py) приводится ниже. Ваше решение должно быть похожим:
from mylist import MyList class MyListSub(MyList):
calls = 0 # Используется всеми экземплярами
def __init__(self, start):
self.adds = 0 # Свой для каждого экземпляра
MyList.__init__(self, start)
def __add__(self, other):
MyListSub.calls = MyListSub.calls + 1 # Счетчик, единый для класса
self.adds = self.adds + 1 # Подсчет экземпляров
return MyList.__add__(self, other)
def stats(self):
return self.calls, self.adds # All adds, my adds if __name__ == '__main__':
x = MyListSub('spam')
y = MyListSub('foo')
print x[2]
print x[1:]
print x + ['eggs']
print x + ['toast']
print y + ['bar']
print x.stats()

Решения упражнений
811
% python mysub.py
a
['p', 'a', 'm']
['s', 'p', 'a', 'm', 'eggs']
['s', 'p', 'a', 'm', 'toast']
['f', 'o', 'o', 'bar']
(3, 2)
4. Методы метакласса. Я решил это упражнение, как показано ни
же. Обратите внимание, что операторы будут извлекать атрибуты с помощью метода __getattr__ – он должен возвращать значение,
чтобы операторы могли работать:
>>> class Meta:
... def __getattr__(self, name):
... print 'get', name
... def __setattr__(self, name, value):
... print 'set', name, value
>>> x = Meta()
>>> x.append
get append
>>> x.spam = "pork"
set spam pork
>>>
>>> x + 2
get __coerce__
Traceback (innermost last):
File "", line 1, in ?
TypeError: call of nonfunction
(TypeError: попытка вызова объекта, не являющегося функцией)
>>>
>>> x[1]
get __getitem__
Traceback (innermost last):
File "", line 1, in ?
TypeError: call of nonfunction
(TypeError: попытка вызова объекта, не являющегося функцией)
>>> x[1:5]
get __len__
Traceback (innermost last):
File "", line 1, in ?
TypeError: call of nonfunction
(TypeError: попытка вызова объекта, не являющегося функцией)
5. Объекты множеств. Ниже приводится сеанс взаимодействия, ко
торый должен у вас получиться. Комментарии описывают, какие методы вызываются.
% python
>>> from setwrapper import Set
>>> x = Set([1, 2, 3, 4]) # Вызывается __init__

812
Приложение B
>>> y = Set([3, 4, 5])
>>> x & y # __and__, intersect, затем __repr__
Set:[3, 4]
>>> x | y # __or__, union, затем __repr__
Set:[1, 2, 3, 4, 5]
>>> z = Set("hello") # __init__ удаляет повторяющиеся символы
>>> z[0], z[ 1] # __getitem__
('h', 'o')
>>> for c in z: print c, # __getitem__
h e l o
>>> len(z), z # __len__, __repr__
(4, Set:['h', 'e', 'l', 'o'])
>>> z & "mello", z | "mello"
(Set:['e', 'l', 'o'], Set:['h', 'e', 'l', 'o', 'm'])
Мой расширенный подкласс, позволяющий обрабатывать сразу не
сколько операндов, приводится ниже (файл multiset.py). В нем по
требовалось изменить всего два метода из оригинального набора.
Строка документирования в классе поясняет принцип его действия:
from setwrapper import Set class MultiSet(Set):
"""
Наследует все атрибуты класса Set, но расширяет методы intersect и union, добавляя возможность обработки нескольких операндов; обратите внимание, что "self" – попрежнему первый аргумент
(теперь сохраняется в списке аргументов *args). Кроме того,
обратите внимание, что теперь унаследованные операторы & и | вызывают новые методы с двумя аргументами, но для одновременной обработки более чем 2 операндов требуется вызов метода, а не выражения:
"""
def intersect(self, *others):
res = []
for x in self: # Сканировать первую последовть
for other in others: # Для всех остальных аргументов
if x not in other: break # Элемент присутствует во всех?
else: # Нет: прервать цикл
res.append(x) # Да: добавить элемент в конец
return Set(res)
def union(*args): # Self  args[0]
res = []
for seq in args: # Для всех аргументов
for x in seq: # Для всех узлов
if not x in res:
res.append(x) # Добавить новый элемент в результат
return Set(res)

Решения упражнений
813
Ваш сеанс взаимодействия с интерактивной оболочкой должен вы
глядеть примерно так, как показано ниже. Обратите внимание, что пересечение двух множеств можно находить как с помощью опера
тора &, так и с помощью метода intersect, но для случая трех и более множеств обязательно нужно вызывать метод intersect – оператор &
является двухместным. Кроме того, обратите внимание, что можно было класс MultiSet назвать просто Set, чтобы сделать это изменение более прозрачным, если бы в файле multiset в качестве имени насле
дуемого класса мы использовали полное имя setwrapper.Set:
>>> from multiset import *
>>> x = MultiSet([1,2,3,4])
>>> y = MultiSet([3,4,5])
>>> z = MultiSet([0,1,2])
>>> x & y, x | y # Два операнда
(Set:[3, 4], Set:[1, 2, 3, 4, 5])
>>> x.intersect(y, z) # Три операнда
Set:[]
>>> x.union(y, z)
Set:[1, 2, 3, 4, 5, 0]
>>> x.intersect([1,2,3], [2,3,4], [1,2,3]) # Четыре операнда
Set:[2, 3]
>>> x.union(range(10)) # Два операнда также допустимы
Set:[1, 2, 3, 4, 0, 5, 6, 7, 8, 9]
6. Связи в дереве классов. Ниже показано, как я изменил класс Lister,
и результат повторного запуска теста. Чтобы дополнительно ото
бразить унаследованные атрибуты, необходимо реализовать метод,
аналогичный текущему методу attrnames, и предусмотреть в нем ре
курсивный обход ссылок __bases__ в каждом классе. Поскольку, на
чиная с версии Python 2.2, функция dir включает в результат унас
ледованные атрибуты, можно было бы просто организовать цикл по ее результатам: например, for x in dir(self) и использовать getattr(self,x)
. Однако такой прием не поможет, если вам потребу
ется вывести структуру дерева классов (как в примере classtree.py
в главе 24):
class Lister:
def __repr__(self):
return ("" %
(self.__class__.__name__, # Имя моего класса
self.supers(), # Мои суперклассы
id(self), # Мой адрес
self.attrnames()) ) # список name=value
def attrnames(self):
...не изменился...
def supers(self):
result = ""
first = 1
for super in self.__class__.__bases__: # Вверх на один уровень

814
Приложение B
if not first:
result = result + ", "
first = 0
result = result + super.__name__ # имя, не repr(super)
return result
C:\python\examples> python testmixin.py
name data3=42
name data2=eggs name data1=spam
>
7. Композиция. Мое решение с комментариями, взятыми из описания,
приводится ниже (файл lunch.py). Это один из случаев, когда свою мысль проще выразить на языке Python, чем на естественном языке:
class Lunch:
def __init__(self): # Создать/встроить Customer и Employee
self.cust = Customer()
self.empl = Employee()
def order(self, foodName): # Начать имитацию оформления заказа
self.cust.placeOrder(foodName, self.empl)
def result(self): # Узнать у клиента название блюда
self.cust.printFood()
class Customer:
def __init__(self): # Инициализировать блюдо значением None
self.food = None def placeOrder(self, foodName, employee): # Передать заказ официанту
self.food = employee.takeOrder(foodName)
def printFood(self): # Вывести название блюда
print self.food.name class Employee:
def takeOrder(self, foodName): # Вернуть блюдо с требуемым названием
return Food(foodName)
class Food:
def __init__(self, name): # Сохранить название блюда
self.name = name if __name__ == '__main__':
x = Lunch() # Программный код самопроверки
x.order('burritos') # Если запускается как сценарий,
x.result() # а не импортируется как модуль
x.order('pizza')
x.result()
% python lunch.py
burritos pizza
8. Классификация животных в зоологии. Ниже приводится мой спо
соб реализации классификации животных на языке Python (файл

Решения упражнений
815
zoo.py
); этот пример достаточно искусственный, но сам принцип применим ко многим реальным ситуациям, начиная от реализации графического интерфейса и заканчивая базами данных сотрудни
ков. Обратите внимание, что в классе Animal выполняется обраще
ние к методу self.speak, это приводит к выполнению независимого поиска метода speak в дереве наследования, начиная с подкласса.
Протестируйте работу этого дерева классов, следуя описанию уп
ражнения. Попробуйте дополнить эту иерархию новыми классами и создать экземпляры различных классов в дереве:
class Animal:
def reply(self): self.speak() # Вызов метода подкласса
def speak(self): print 'spam' # Собственное сообщение
class Mammal(Animal):
def speak(self): print 'huh?'
class Cat(Mammal):
def speak(self): print 'meow'
class Dog(Mammal):
def speak(self): print 'bark'
class Primate(Mammal):
def speak(self): print 'Hello world!'
class Hacker(Primate): pass # Наследует класс Primate
9. Сценка с мертвым попугаем. Ниже приводится мое решение этого упражнения (файл parrot.py). Обратите внимание на то, как работа
ет метод line в суперклассе Actor: он дважды обращается к атрибу
там аргумента self, дважды отсылая интерпретатор к экземпляру,
и тем самым дважды инициирует поиск в дереве наследования –
self.name и self.says(), с целью отыскать требуемую информацию в подклассах:
class Actor:
def line(self): print self.name + ':', repr(self.says())
class Customer(Actor):
name = 'customer'
def says(self): return "that's one exbird!"
class Clerk(Actor):
name = 'clerk'
def says(self): return "no it isn't..."
class Parrot(Actor):
name = 'parrot'
def says(self): return None class Scene:
def __init__(self):
self.clerk = Clerk() # Встраивание некоторых экземпляров
self.customer = Customer() # Scene – это составной объект

816
Приложение B
self.subject = Parrot()
def action(self):
self.customer.line() # Делегировать выполнение встроенным
self.clerk.line() # экземплярам
self.subject.line()
Часть VII. Исключения и инструменты
Упражнения находятся в главе 29, в разделе «Упражнения к седьмой части».
1. try/except. Ниже приводится моя версия функции oops (файл
oops.py
). Что касается вопросов, не связанных с реализацией про
граммного кода: если изменить функцию oops так, чтобы вместо In
dexError она возбуждала исключение KeyError, то обработчик в инст
рукции try не сможет перехватывать исключение (оно достигнет верхнего уровня и приведет к появлению сообщения об ошибке по умолчанию). Имена KeyError и IndexError определены во встроенной области видимости. Импортируйте модуль __builtin__ и передайте его функции dir, чтобы убедиться в этом:
def oops():
raise IndexError def doomed():
try:
oops()
except IndexError:
print 'caught an index error!'
else:
print 'no error caught...'
if __name__ == '__main__': doomed()
% python oops.py
caught an index error!
2. Объекты исключений и списки. Ниже показано, как я дополнил модуль своим собственным исключением (для начала исключение определено в виде строки):
MyError = 'hello'
def oops():
raise MyError, 'world'
def doomed():
try:
oops()
except IndexError:
print 'caught an index error!'
except MyError, data:
print 'caught error:', MyError, data

Решения упражнений
817
else:
print 'no error caught...'
if __name__ == '__main__':
doomed()
% python oops.py
caught error: hello world
Чтобы идентифицировать исключение как класс, я просто изменил первую часть файла, как показано ниже, и сохранил его под име
нем oop_oops.py:
class MyError: pass def oops():
raise MyError()
...остальная часть осталась без изменений...
Как и в случае любого другого исключения на основе класса, в виде дополнительных данных передается сам экземпляр – теперь сооб
щение об ошибке содержит имя класса и указание на его экземпляр
(<...>).
% python oop_oops.py
caught error: __main__.MyError <__main__.MyError instance at 0x00867550>
Не забывайте, что внешний вид сообщения можно улучшить, если определить метод __repr__ или __str__ в своем классе так, чтобы он возвращал нужную строку. Подробнее об этом рассказывается в гла
ве 24.
3. Обработка ошибок. Ниже приводится мое собственное решение этого упражнения (файл safe2.py). Я добавил тесты непосредствен
но в файл, чтобы не проводить их в интерактивной оболочке, но ре
зультаты от этого не меняются.
import sys, traceback def safe(entry, *args):
try:
apply(entry, args) # Перехватывать любые исключения
except:
traceback.print_exc()
print 'Got', sys.exc_type, sys.exc_value import oops safe(oops.oops)
% python safe2.py
Traceback (innermost last):
File "safe2.py", line 5, in safe apply(entry, args) # Перехватывать любые исключения
File "oops.py", line 4, in oops raise MyError, 'world'

818
Приложение B
hello: world
Got hello world
Сегодня я реализовал бы эту функцию, как показано ниже, исполь
зуя новый синтаксис *args и функцию exc_info:
def safe(entry, *args):
try:
entry(*args) # Перехватывать любые исключения
except:
traceback.print_exc()
print 'Got', sys.exc_info(){0], sys.exc_info()[1]
4. Далее приводится несколько примеров для самостоятельного изу
чения на досуге; еще больше примеров программ на языке Python вы найдете в последующих книгах и в Сети:
# Поиск наибольшего файла в единственном каталоге
dirname = r'C:\Python25\Lib'
import os, glob allsizes = []
allpy = glob.glob(os.path.join(dirname, '*.py'))
for filename in allpy:
filesize = os.path.getsize(filename)
allsizes.append((filesize, filename))
allsizes.sort()
print allsizes[:2]
print allsizes[2:]
# Поиск наибольшего файла в дереве каталогов
import sys if sys.platform[:3] == 'win':
dirname = r'C:\Python25\Lib'
else:
dirname = '/usr/lib/python'
import os, glob allsizes = []
for (thisDir, subsHere, filesHere) in os.walk(dirname):
for filename in filesHere:
if filename.endswith('.py'):
fullname = os.path.join(thisDir, filename)
fullsize = os.path.getsize(fullname)
allsizes.append((fullsize, fullname))
allsizes.sort()
print allsizes[:2]
print allsizes[2:]
# Поиск наибольшего файла с исходным программным кодом на языке Python
# в пути поиска модулей
import sys, os, pprint

Решения упражнений
819
visited = {}
allsizes = []
for srcdir in sys.path:
for (thisDir, subsHere, filesHere) in os.walk(srcdir):
thisDir = os.path.normpath(thisDir)
if thisDir.upper() in visited:
continue else:
visited[thisDir.upper()] = True for filename in filesHere:
if filename.endswith('.py'):
pypath = os.path.join(thisDir, filename)
try:
pysize = os.path.getsize(pypath)
except:
print 'skipping', pypath allsizes.append((pysize, pypath))
allsizes.sort()
pprint.pprint(allsizes[:3])
pprint.pprint(allsizes[3:])
# Сумма по столбцам, разделенным запятыми, в текстовом файле
filename = 'data.txt'
sums = {}
for line in open(filename):
cols = line.split(',')
nums = [int(col) for col in cols]
for (ix, num) in enumerate(nums):
sums[ix] = sums.get(ix, 0) + num for key in sorted(sums):
print key, '=', sums[key]
# То же, что и выше, но суммы накапливаются в списке, а не в словаре
import sys filename = sys.argv[1]
numcols = int(sys.argv[2])
totals = [0] * numcols for line in open(filename):
cols = line.split(',')
nums = [int(x) for x in cols]
totals = [(x + y) for (x, y) in zip(totals, nums)]
print totals
# Регрессивное тестирование результатов работы нескольких сценариев
import os testscripts = [dict(script='test1.py', args=''),
dict(script='test2.py', args='spam')]
for testcase in testscripts:

820
Приложение B
commandline = '%(script)s %(args)s' % testcase output = os.popen(commandline).read()
result = testcase['script'] + '.result'
if not os.path.exists(result):
open(result, 'w').write(output)
print 'Created:', result else:
priorresult = open(result).read()
if output != priorresult:
print 'FAILED:', testcase['script']
print output else:
print 'Passed:', testcase['script']
# Создание ГИП с помощью Tkinter: кнопка, изменяющая цвет и размер
from Tkinter import *
import random fontsize = 25
colors = ['red', 'green', 'blue', 'yellow', 'orange',
'white', 'cyan', 'purple']
def reply(text):
print text popup = Toplevel()
color = random.choice(colors)
Label(popup, text='Popup', bg='black', fg=color).pack()
L.config(fg=color)
def timer():
L.config(fg=random.choice(colors))
win.after(250, timer)
def grow():
global fontsize fontsize += 5
L.config(font=('arial', fontsize, 'italic'))
win.after(100, grow)
win = Tk()
L = Label(win, text='Spam',
font=('arial', fontsize, 'italic'), fg='yellow', bg='navy', relief=RAISED)
L.pack(side=TOP, expand=YES, fill=BOTH)
Button(win, text='press', command=(lambda: reply('red'))).pack(side=BOTTOM, fill=X)
Button(win, text='timer', command=timer).pack(side=BOTTOM, fill=X)
Button(win, text='grow' , command=grow).pack(side=BOTTOM, fill=X)
win.mainloop()
# То же, что и выше, но на основе классов, поэтому каждое окно может
# иметь свою собственную информацию о состоянии
from Tkinter import *
import random

Решения упражнений
821
class MyGui:
"""
ГИП с кнопками, которые изменяют цвет и размер надписи """
colors = ['blue', 'green', 'orange', 'red', 'brown', 'yellow']
def __init__(self, parent, title='popup'):
parent.title(title)
self.growing = False self.fontsize = 10
self.lab = Label(parent, text='Gui1', fg='white', bg='navy')
self.lab.pack(expand=YES, fill=BOTH)
Button(parent, text='Spam', command=self.reply).pack(side=LEFT)
Button(parent, text='Grow', command=self.grow).pack(side=LEFT)
Button(parent, text='Stop', command=self.stop).pack(side=LEFT)
def reply(self):
" при нажатии кнопки Spam изменяет цвет случайным образом "
self.fontsize += 5
color = random.choice(self.colors)
self.lab.config(bg=color,
font=('courier', self.fontsize, 'bold italic'))
def grow(self):
"при нажатии кнопки Grow начинает увеличивать размер надписи"
self.growing = True self.grower()
def grower(self):
if self.growing:
self.fontsize += 5
self.lab.config(font=('courier', self.fontsize, 'bold'))
self.lab.after(500, self.grower)
def stop(self):
"при нажатии кнопки Stop останавливает увеличение размера"
self.growing = False class MySubGui(MyGui):
colors = ['black', 'purple'] # Настройка изменения цвета
MyGui(Tk(), 'main')
MyGui(Toplevel())
MySubGui(Toplevel())
mainloop()
# Сканирование и обслуживание ящика электронной почты
"""
Проверяет ящик входящей электронной почты, извлекает заголовки писем,
позволяет удалять сообщения, не загружая их полностью """
import poplib, getpass, sys mailserver = 'здесь требуется указать имя почтового сервера pop'
# pop.rmi.net

822
Приложение B
mailuser = 'здесь требуется указать имя пользователя' # brian
mailpasswd = getpass.getpass('Password for %s?' % mailserver)
print 'Connecting...'
server = poplib.POP3(mailserver)
server.user(mailuser)
server.pass_(mailpasswd)
try:
print server.getwelcome()
msgCount, mboxSize = server.stat()
print 'There are', msgCount, 'mail messages, size ', mboxSize msginfo = server.list()
print msginfo for i in range(msgCount):
msgnum = i+1
msgsize = msginfo[1][i].split()[1]
resp, hdrlines, octets = server.top(msgnum, 0) # Get hdrs only print ''*80
print '[%d: octets=%d, size=%s]' % (msgnum, octets, msgsize)
for line in hdrlines: print line if raw_input('Print?') in ['y', 'Y']:
for line in server.retr(msgnum)[1]: print line # Получить сообщ.
if raw_input('Delete?') in ['y', 'Y']:
print 'deleting'
server.dele(msgnum) # Удалить на сервере
else:
print 'skipping'
finally:
server.quit() # Закрыть почтовый ящик
raw_input('Bye.') # Предотвратить самопроизвольное закрытие окна
# CGI сценарий для взаимодействия с броузером на стороне клиента
#!/usr/bin/python import cgi form = cgi.FieldStorage() # Разбор данных формы
print "Contenttype: text/html\n" # заголовок и пустая строка
print ""
print "" # разметка html страницы
print ""
if not form.has_key('user'):
print "
1   ...   90   91   92   93   94   95   96   97   98


написать администратору сайта