Лутц М. - Изучаем Python (1077325), страница 84
Текст из файла (страница 84)
Например: »> х = С(зев(3.14, 4) № Сохранить обьект резул~тата »> х 12, 55 Теперь посмотрим, что произойдет, если функции передать объекты совершенно разных типов: »> асеев('И1', 4) 'Изцзнтцт' № Фумкции ие ииеют типа На этот раз функция выполнила нечто совершенно иное. Вместо двух чисел в аргументах х и у функции были переданы строка и целое число. Вспомните, что оператор * может работать как с числами, так и с последовательностями; поскольку в языке Ру(акоп не требуется объявлять типы переменных, аргументов или возвращаемых значений, мы можем использовать функцию Ссиеб для умножения чисел и повторения последовательностей. Другими словами, смысл функции сзаез и тип возвращаемого значения определяются аргументами, которые ей передаются, Это основная 392 Глава 15. Основы функций идея языка Руспоп (и, возможно, ключ к использованию языка), которую мы рассмотрим в следующем разделе. Полиморфизм в языке РуФол Как мы только что видели, смысл выражения х ° у в нашей простой функции с(аез полностью зависит от типов объектов х и у — одна и та же функция может выполнять умножение в одном случае и повторение в другом.
В языке Рубанов именно объекты определяют синтаксический смысл операции. В действительности оператор ° вЂ” это всего лишь указание для обрабатываемых объектов. Такого рода зависимость от типов известна как полиморфизм — термин, впервые встретившийся нам в главе 4, который означает, что смысл операции зависит от типов обрабатываемых объектов. Поскольку Ру(поп— это язык с динамической типизацией, полиморфизм в нем проявляется повсюду.
Фактически все операции в языке РуьЬоп являются полиморфическими: вывод, извлечение элемента, оператор ° и многие другие. Такое поведение заложено в язык изначально и объясняет в большой степени его краткость и гибкость. Например, единственная функция может автоматически применяться к целой категории типов объектов. Пока объекты поддерживают ожидаемый интерфейс (или протокол), функция сможет обрабатывать их. То есть, если объект, передаваемый функции, поддерживает ожидаемые методы и операторы выражений, он будет совместим с логикой функции.
Даже в случае с нашей простой функцией это означает, что любые два объекта, поддерживающие оператор *, смогут обрабатываться функцией, и неважно, что они из себя представляют и когда были созданы. Эта функция будет работать с числами (выполняя операцию умножения), с двумя строками или со строкой и числом (выполняя операцию повторения) и с любыми другими комбинациями объектов, поддерживающими ожидаемый интерфейс — даже с объектами, порожденными на базе классов, которые мы еще пока не создали. Кроме того, если функции будут переданы объекты, которые яе поддерживают ожидаемый интерфейс, интерпретатор обнаружит ошибку при выполнении выражения * и автоматически возбудит исключение.
Поэтому для нас совершенно бессмысленно предусматривать проверку на наличие ошибок в программном коде. Фактически, добавив такую проверку, мы ограничим область применения нашей функции, так как она сможет работать только с теми типами объектов, которые мы предусмотрели. Это важнейшее отличие философии языка РуьЬоп от языков программирования со статической типизацией, таких как С++ и дача: программный код на языке Ру(поп не делает предположений о конкретных типах данных. В противном случае он сможет работать только с теми типами данных, которые ожидались на момент его написания, и он не будет поддерживать объекты других совместимых типов, кото- зйз Второй пример: пересечение последовательностей рые могут быть созданы в будущем.
Проверку типа объекта можно выполнить с помощью таких средств, как встроенная функция туре„но в этом случае программный код потеряет свою гибкость. Вообще говоря, при программировании на языке РуФ)топ во внимание принимаются интерфейсы объектов, а не типы данных. Конечно, такая модель полиморфизма предполагает необходимость тестирования программного кода на наличие ошибок, так как из-за отсутствия объявлений типов нет возможности с помощью компилятора выявить некоторые виды ошибок на ранней стадии.
Однако в обмен на незначительное увеличение объема отладки мы получаем существенное уменьшение объема программного кода, который требуется написать, и существенное увеличение его гибкости. На практике это означает чистую победу. Второй пример: пересечение последовательностей Рассмотрим второй пример функции, которая делает немного больше, чем простое умножение аргументов, и продолжает иллюстрацию основ функций.
В главе 13 мы написали цикл тот, который выбирал элементы, общие для двух строк. Там было замечено, что полезность этого программного кода не так велика, как могла бы быть, потому что он может работать только с определенными переменными и не может быть использован повторно. Беэусловно, можно было бы просто скопировать этот блок кода и вставлять его везде, где потребуется, но такое решение нельзя признать ни удачным, ни универсальным — нам по-прежнему придется редактировать каждую копию, изменяя имена последовательностей; изменение алгоритма также влечет за собой необходимость вносить изменения в каждую копию.
Определение К настоящему моменту вы уже наверняка поняли, что решение этой дилеммы заключается в том, чтобы оформить этот цикл тот в виде функции. Такой подход несет нам следующие преимущества: ° Оформив программный код в виде функции, мы получаем возможность использовать его столько раз, сколько потребуется. ° Так как вызывающая программа может передавать функции произвольные аргументы, функция сможет использоваться с любыми двумя последовательностями (или итерируемыми объектами) для получения их пересечения.
° Когда логика работы оформлена в виде функции, достаточно изменить программный код всего в одном месте, чтобы изменить способ получения пересечения. 394 Глава 15. Основы функций ° Поместив функцию в файл модуля, ее можно будет импортировать и использовать в любой программе на вашем компьютере. В результате программный код, обернутый в функцию, превращается в универсальную утилиту нахождения пересечения: В том, чтобы преобразовать фрагмент кода из главы 13 в функцию, нет ничего сложного — мы просто вложили оригинальную реализацию в инструкцию бе[ и присвоили имена объектам, с которыми она работает. Поскольку эта функция возвращает результат, мы также добавили инструкцию гесцгп, которая возвращает полученный объект результата вызывающей программе.
Вызов Прежде чем функцию можно будет вызвать, ее необходимо создать. Для этого нужно выполнить инструкцию бе[, либо введя ее в интерактивной оболочке, либо поместив файл модуля и выполнив операцию импорта. Как только инструкция бег будет выполнена, можно будет вызывать функцию и передать ей два объекта последовательностей в круглых скобках: й Строки В данном примере мы передали функции две строки и получили список общих символов.
Алгоритм работы функции можно выразить простой фразой: «Для всех элементов первого аргумента, если этот элемент присутствует и во втором аргументе, добавить его в конец результата». Этот алгоритм на языке РуФ]зоп описывается немного короче, чем на естественном языке, но работает он точно так же.
Еще о полиморфизме Как и любая другая функция в языке Рус]>оп, функция зпге свес( так- же является полиморфной. То есть она может обрабатывать объекты произвольных типов при условии, что они поддерживают ожидаемый интерфейс: »> х = [пгегзест([1, 2, 3], (1, 4)) й сиевиаание типов >» х Ф Сокраненний обьект с результатои [1] Сет тптегзест(зео1, зео2). гез = [] Гог х тп зеа1 тт х Ю ЗЕС2: гез аррепо(х) гетцгп гев »> з1 = "ЗРАИ" . » з2 = "ьСАИ" »> 1псегзес1(з1, з2) ['5', 'А', 'И'] й Изначально пустой результат й Обход последовательности зес1 Ф Обаий элеиентр й ))сбавить а конец 395 Второй пример: пересечение последовательностей На этот раз функции были переданы объекты разных типов — список и кортеж — и это не помешало ей отыскивать общие элементы. Так как отсутствует необходимость предварительного объявления типов аргументов, функция 1птегвест благополучно будет выполнять итерации по объектам последовательностей любых типов при условии, что они будут поддерживать ожидаемые интерфейсы.
Для функции! псегвест это означает, что первый объект должен обладать поддержкой циклов тог, а второй — поддержкой оператора 1п, выполняющего проверку на вхождение. Любые два объекта, отвечающие этим условиям, будут обработаны независимо от их типов, включая как сами последовательности, такие как строки и списки, так и любые итерируемые объекты, с которыми мы встречались в главе 13, включая файлы, словари и даже объекты, созданные на основе классов и использующие перегрузку операторов (эту тему мы будем рассматривать в шестой части книги).' И снова, если функции передать объекты, которые не поддерживают эти интерфейсы (например, числа), интерпретатор автоматически обнаружит несоответствие и возбудит исключение, т. е.
именно то, что нам требуется, и это лучше, чем добавление явной проверки типов аргументов. Отказываясь от реализации проверки типов и позволяя интерпретатору самому обнаруживать несоответствия, мы тем самым уменьшаем объем программного кода и повышаем его гибкость. Локальные переменные Переменная гев внутри функции т псе гвест — это то, что в языке Рус)топ называется локальной переменной, — имя, которое доступно только программному коду внутри инструкции се[ и существует только во время выполнения функции. Фактически, любые имена, которым тем или иным способом были присвоены некоторые значения внутри функции, по умолчанию классифицируются как локальные переменные.
Дза важных замечания. Первое: с технической точки зрения файл может использоваться только в качестве первого аргумента функции тптегзест, потому что во время первого же применения оператора! и файл будет просканирозан до конца. Например, такой вызов, как 1птегзест(преп('Саган тхт ), ['1тпет1п', '1тпе21п', '1тпе31п')), будет работать, но вызов (птегзест(орел ( сата1.тхт), преп('сатз2.тхт')) — нет, за исключением случая, когда первый файл содержит единственную строку. Для настоящих последовательностей итерационный контекст вызывает метод ттег для получения итера- тора, который всегда переустанавлнвается з начало последовательности, но операции открытия н чтения файла приводят к исчерпанию итератора.