Саммерфилд - Программирование на Python 3 (1077331), страница 104
Текст из файла (страница 104)
В конце функция-декоратор возвращает модифицированный класс, а функция ча1Ы всг!пц() возвращает функцию-декоратор. Функция ча1!б поасег() по своей структуре идентична функции ча- 110 в(г!пц(), она отличается только принимаемыми аргументами и реализацией проверки в функции записи, поэтому мы не будем показывать ее здесь.
(Полный программный код примера вы найдете в файле )гайд.ру.) Последнее, что нам осталось описать, — это дескриптор Вепег!сдевсырсог; и зто, как оказывается, самая простая часть примера: о1авв Овпвгсодввсгсрсог; бвг спсс (ве11, цвссег, вессвг): ве1с.цвссег = дессег ве1г.ввсСег = веССвг бег дв1 (вв11, !пвсвпсв, онпег=иопе): 464 Глава 8. Усовершенствованные приемы программирования 1Г 1патапсе (а Иппе: гетогп ае1Г гетигп ае1г.зеттег( (патапсе) сег вет (ае1(, тпатапсе, ча1пе): гетпгп ае1г.аеттег(1патапсе, ча1пе) Дескриптор используется для хранения функций чтения и записи для каждого атрибута и просто передает всю работу по чтению и записи этим функциям.
В заключение В этой главе мы получили массу дополнительных сведений о поддержке процедурного и объектно-ориентированного программирования в языке Ру(Ьоп и приобрели некоторое представление о поддержке функционального программирования. В первом разделе мы узнали, как создавать выражения-генераторы, и познакомились с функциями-генераторами поближе.
Мы также узнали, как динамически импортировать модули и как получать доступ к функциональным возможностям таких модулей, а также динамически выполнять программный код. В этом разделе мы увидели примеры создания и использования рекурсивных функций и нелокальных переменных.
Мы также узнали, как создавать собственные декораторы функций и методов и как определять и и использовать аннотации функций. Во втором разделе главы мы исследовали различные дополнительные аспекты объектно-ориентированного программирования. Сначала мы больше узнали о доступе к атрибутам, например, с помощью специального метода сетаттг ().
Затем мы познакомились с функторами и увидели, как они могут использоваться для получения функций с состоянием, что также может быть достигнуто посредством добавления свойств к функции или за счет использования замыканий — оба приема также рассматриваются в этой главе. Мы узнали, как использовать инструкцию н1(п вместе с менеджерами контекста и как создавать собственные менеджеры контекста. Поскольку объекты файлов в языке РуФ)топ, помимо всего прочего, являются еще и менеджерами контекста, мы можем выполнять операции с файлами с использованием структур тгу н(1)т ...
ехсерт, чтобы гарантировать закрытие открытых файлов без необходимости реализовать блоки (1па11у. Затем во втором разделе мы перешли к описанию дополнительных особенностей объектно-ориентированного программирования, начав с описания дескрипторов. Дескрипторы могут использоваться самыми разными способами, и эта технология лежит в основе многих стандартных декораторов Ру(Ьоп, таких как Юргорег(у и эс1аваае(11ас. Мы узнали, как создавать собственные дескрипторы, и увидели три различных примера их использования. Затем мы исследовали декораторы клас- 465 Упражнения сов и увидели, что с их помощью можно модифицировать классы почти так же, как с помощью декораторов функций можно модифицировать функции.
В последних трех подразделах второго раздела мы познакомились с поддержкой в языке РуФЬоп абстрактных базовых классов, множественным наследованием и метаклассами. Мы узнали, как создавать собственные классы, использующие стандартные абстрактные базовые классы, и как создавать собственные абстрактные классы. Мы также увидели, как использовать множественное наследование для объединения в одном классе возможностей нескольких классов. А из описания метаклассов мы узнали, как оказывать влияние на процесс создания и инициализации классов (в противоположность экземплярам классов).
В предпоследнем разделе были представлены некоторые функции и модули, которые в языке РУ1Ьоп обеспечивают поддержку функционального программирования. Мы узнали, как использовать распространенные идиомы функционального программирования, такие как отображение, фильтрация и упрощение. Мы также увидели, как создавать частично подготовленные функции.
В последнем разделе было показано, как, объединив декораторы классов и дескрипторы, можно реализовать мощный и гибкий механизм создания атрибутов со встроенной проверкой значений. Эта глава завершает описание самого языка программирования Ру1Ьоп. Не все особенности языка были рассмотрены в этой и в предыдущих главах, но особенности, котрые не были охвачены нами, используются очень редко. Ни в одной из последующих глав не будет представлено новых особенностей языка, однако во всех этих главах будут использоваться модули из стандартной библиотеки, которые не были описаны прежде, и в некоторых из них будут использоваться приемы, продемонстрированные в этой и в предыдущих главах.
Кроме того, в программах, которые будут демонстрироваться в следующих главах, отсутствуют ограничения, применявшиеся ранее (то есть ограничения на использование только тех аспектов языка, которые были представлены к текущему моменту), поэтому они являются наиболее характерными примерами в этой книге. Упражнения Ни одно из упражнений, которые приводятся здесь, не требует создания большого объема программного кода, но ни одно из них нельзя назвать легким! 1. Скопируйте программу шая1с-пшпЬегз.ру и удалите ее функции пеГ 1эпс11оп() и все функции 1сас зопп1еэ(), за исключением какой- нибудь одной.
Добавьте класс функтора беггппст(оп с двумя кашами: один — для хранения найденных функций и другой — для хране- 466 Глава 8. Усовершенствованные приемы программирования ния функций, которые не были найдены (чтобы избежать повторного поиска функций в модулях, где эти функции отсутствуют). Единственное изменение в функции аа1п() заключается в добавлении строки де( Гппсттоп = детГппсттоп() передциклом и виспользовании инструкции и1(П, чтобы можно было отказаться от блока (1- пв11у. Кроме того, проверьте, что функции в модуле являются вызываемыми объектами, но не с помощью функции пвзвттг(), а с помощью проверки на принадлежность абстрактному базовому классу со11есттопв. Са11ас)е.
Определение класса можно уместить примерно в двенадцать строк программного кода. Решение приводится в файле тая(с-иитЬегз апз.ру. 2. Создайте новый файл модуля и определите в нем три функции: 1з взсй(), которая возвращает Тгве, если все символы в заданной строке имеют числовые коды меньше 127; 1в авст1 рспстват!сп(), которая возвращает Тгсе, если все символы в заданной строке содержатся и в строке втг1пд.рспстсаттсп; 1з взст1 ргтптвв)е(), которая возвращает Тгсе, если все символы в заданной строке содержатся ив строке зтг1пд.рг1птас1е.
Последние две функции структурно идентичны друг другу. Каждая функция должна быть создана с использованием инструкции 1аавсв, может занимать одну или две строки и должна быть написана в функциональном стиле. Обязательно добавьте для каждой функции строки документирования с доктестами и предусмотрите запуск доктестов при попытке запустить модуль. Для реализации каждой функции потребуется от трех до пяти строк программного кода, а с учетом доктестов общий размер модуля не должен превышать 25 строк. Решение приводится в файле Азой.ру. 3.
Создайте новый файл модуля и определите в ием класс Асса(с менеджера контекста. Этот класс должен работать подобно классу Атоа(с((зт, демонстрировавшемуся в этой главе, за исключением того, что он должен работать не только со списками, но и с любыми типами изменяемых коллекций. Метод 1п(1 () должен проверять тип контейнера, и, вместо того чтобы хранить флаг выбора между поверхностной и глубокой копиями, он должен в зависимости от значения флага присваивать соответствующую функцию атрибуту зе1г. сору и вызывать функцию копирования в методе ептег (). Метод ех11 () имеет немного более сложную реализацию, потому что замена содержимого списка выполняется иначе, чем замена содержимого словарей и множеств, и здесь нельзя использовать инструкцию присваивания, потому что она никак не отразится на оригинальном контейнере. Определение самого класса можно уместить примерно в тридцать строк, однако вам необходимо также добавить доктесты.
Решение приводится в файле Агоиис.ру, длина которого составляет около ста пятидесяти строк, включая доктесты. Процессы и потоки С тех пор, как многоядерные процессоры получили широкое распространение, еще более важной и более практически значимой стала проблема распределения вычислительной нагрузки таким образом, чтобы получить максимальную отдачу от всех имеющихся ядер. На практике используются два основных подхода к распределению нагрузки. Один из них заключается в одновременном выполнении нескольких процессов, а другой — в одновременном выполнении нескольких потоков управления.
В этой главе будет продемонстрировано, как использовать оба подхода. Преимущество выполнения нескольких процессов, то есть запуск автономных программ, заключается в том, что каждый процесс работает независимо от других. Тем самым все бремя разрешения конфликтов ложится на операционную систему. Недостаток такого подхода заключается в неудобстве организации взаимодействий и совместного использования данных между вызывающей программой и отдельными процессами. В системах БМ1Х запуск отдельных процессов может выполняться с использованием парадигмы ветвления процессов, но для кросс-платформенных программ должно использоваться другое решение. Простейшее решение, которое будет показано здесь, заключается в том, что вызывающая программа передает данные запускаемым ею процессам и оставляет за ними возможность самостоятельно воспроизвести результаты.
Наиболее гибкое решение, существенно упрощающее двусторонний обмен данными, заключается в использовании механизмов сетевых взаимодействий. Конечно, во многих ситуациях втаком взаимодействии нет никакой необходимости, когда вполне достаточно запустить одну или более программ с помощью управляющей программы. Альтернативой выполнению работы независимыми процессами является создание многопоточных программ, которые распределяют рабо- 468 Глава 9.
Процессы и потоки ту между независимыми потоками выполнения. Преимуществом такого подхода является простота совместного использования данных (если при этом гарантируется, что в каждый конкретный момент времени доступ к данным имеет только один поток выполнения), но в этом случае все бремя разрешения конфликтов ложится на плечи программиста. В языке Ру1)топ имеется отличная поддержка создания многопоточных программ, позволяющая свести к минимуму работу, которую нам необходимо выполнить самостоятельно.
Тем не менее многопоточные программы намного сложнее однопоточных программ и требуют значительно большей аккуратности при их создании и сопровождении. В первом разделе этой главы мы создадим две маленькие программы. Первая программа будет запускаться пользователем, а вторая — первой программой, причем вторая программа будет запускаться в виде отдельного процесса. Второй раздел начнется с введения в многопоточное программирование. После этого мы создадим многопоточную программу, реализующую ту же функциональность, что и две программы из первого раздела, с целью продемонстрировать различия между решениями, основанными на использовании нескольких процессов и нескольких потоков выполнения.