Математический анализ. 3е издание
Скачать 4.86 Mb.
|
• Аргументы передаются посредством присваивания (в виде ссылок на объекты). В языке Python аргументы передаются функциям по средством выполнения операции присваивания (что, как мы уже знаем, означает – в виде ссылок на объекты). Как будет показано далее, модель, принятая в языке Python, в действительности не эк вивалентна правилам передачи аргументов по ссылке в языке C или C++ – и вызывающая программа, и функция совместно используют ссылку на объект, но здесь нет никакого совмещения имен. Измене ние имени аргумента также не изменяет имени в вызывающей про грамме, но модификация изменяемых объектов внутри функции может приводить к изменению объектов в вызывающей программе. • global объявляет переменные, глобальные для модуля, без при! сваивания им значений. По умолчанию все имена, присваивание которым производится внутри функций, являются локальными для этих функций и существуют только во время выполнения функций. Чтобы присвоить значение имени в объемлющем модуле, функция должна объявить его с помощью инструкции global. Гово ря в более широком смысле, поиск имен всегда производится в об+ ласти видимости – там, где хранятся переменные, – а операция присваивания связывает имена с областями видимости. • Аргументы, возвращаемые значения и переменные не объявляют! ся. Как и повсюду в языке Python, на функции также не накладыва ется никаких ограничений по типу. Фактически никакие элементы функций не требуют предварительного объявления: вы можете пе редавать функции аргументы любых типов, возвращать из функции объект любого типа и т. д. Как следствие этого одна и та же функция может применяться к объектам различных типов – допустимыми считаются любые объекты, поддерживающие совместимые интер фейсы (методы и выражения), независимо от конкретного типа. Если чтото из сказанного выше вам показалось непонятным, не вол нуйтесь – в этой части книги мы исследуем все эти концепции на при мерах программного кода. А теперь начнем изучение некоторых из этих идей и рассмотрим несколько примеров. Создание функций 389 Инструкция def Инструкция def создает объект функции и связывает его с именем. В общем виде инструкция имеет следующий формат: def Как и все составные инструкции в языке Python, инструкция def со стоит из строки заголовка и следующего за ней блока инструкций, обычно с отступами (или простая инструкция вслед за двоеточием). Блок инструкций образует тело функции, т. е. программный код, ко торый выполняется интерпретатором всякий раз, когда производится вызов функции. В строке заголовка инструкции def определяются имя функции, с ко торым будет связан объект функции, и список из нуля или более аргу+ ментов (иногда их называют параметрами) в круглых скобках. Имена аргументов в строке заголовка будут связаны с объектами, передавае мыми в функцию, в точке вызова. Тело функции часто содержит инструкцию return: def return Инструкция return может располагаться в любом месте в теле функ ции – она завершает работу функции и передает результат вызываю щей программе. Инструкция return содержит объектное выражение, которое дает результат функции. Инструкция return является необя зательной – если она отсутствует, работа функции завершается, когда поток управления достигает конца тела функции. С технической точ ки зрения, функция без инструкции return автоматически возвращает объект None, однако это значение обычно просто игнорируется. Функции могут также содержать инструкции yield, которые исполь зуются для воспроизведения серии значений с течением времени, од нако их рассмотрение мы отложим до главы 17, где обсуждаются рас ширенные темы, касающиеся функций. Инструкции def исполняются во время выполнения Инструкция def в языке Python – это настоящая исполняемая инструк ция: когда она исполняется, она создает новый объект функции и при сваивает этот объект имени. (Не забывайте, все, что имеется в языке Python, относится ко времени выполнения, здесь нет понятия времени компиляции.) Будучи инструкцией, def может появляться везде, где могут появляться инструкции, – даже внутри других инструкций. На пример, даже при том, что инструкции def обычно исполняются, когда выполняется импорт вмещающего их модуля, допускается вкладывать 390 Глава 15. Основы функций определения функций внутрь инструкций if, что позволяет произво дить выбор между альтернативами: if test: def func(): # Определяет функцию таким способом else: def func(): # Или таким способом func() # Вызов выбранной версии Чтобы понять этот фрагмент программного кода, обратите внимание, что инструкция def напоминает инструкцию присваивания =: она про сто выполняет присваивание во время выполнения. В отличие от ком пилирующих языков, таких как C, функции в языке Python не долж ны быть полностью определены к моменту запуска программы. Други ми словами, инструкции def не интерпретируются, пока они не будут достигнуты и выполнены потоком выполнения, а программный код внутри инструкции def не выполняется, пока функция не будет вызва на позднее. Так как определение функции происходит во время выполнения, имя функции не является однозначно определенным. Важен только объ ект, на который ссылается имя: othername = func # Связывание объекта функции с именем othername() # Вызов функции В этом фрагменте функция была связана с другим именем и вызвана уже с использованием нового имени. Как и все остальное в языке Py thon, функции – это обычные объекты; они явно записываются в па мять во время выполнения программы. Первый пример: определения и вызовы Кроме таких концепций времени выполнения (которые кажутся наи более уникальными для программистов, имеющих опыт работы с тра диционными компилирующими языками программирования) в ис пользовании функций нет ничего сложного. Давайте напишем первый пример, в котором продемонстрируем основы. Как видите, функции имеют две стороны: определение (инструкция def, которая создает функцию) и вызов (выражение, которое предписывает интерпретатору выполнить тело функции). Определение Ниже приводится определение в интерактивной оболочке, которое определяет функцию с именем times. Эта функция возвращает резуль тат обработки двух аргументов: Первый пример: определения и вызовы 391 >>> def times(x, y): # Создать функцию и связать ее с именем ... return x * y # Тело, выполняемое при вызове функции Когда интерпретатор достигает эту инструкцию def и выполняет ее, он создает новый объект функции, в который упаковывает программный код функции и связывает объект с именем times. Как правило, такие инструкции размещаются в файлах модулей и выполняются во время импортирования модулей, однако такую небольшую функцию можно определить и в интерактивной оболочке. Вызов После выполнения инструкции def вы сможете вызывать функцию в своей программе, добавляя круглые скобки после ее имени. В круг лых скобках можно указать один или более аргументов, значения ко торых будут присвоены именам, указанным в заголовке функции: >>> times(2, 4) # Аргументы в круглых скобках 8 Данное выражение передает функции times два аргумента. Как уже упоминалось ранее, передача аргументов осуществляется за счет вы полнения операции присваивания, таким образом, имени x в заголов ке функции присваивается значение 2, а имени y – значение 4, после чего запускается тело функции. В данном случае тело функции состав ляет единственная инструкция return, которая отправляет обратно ре зультат выражения. Возвращаемый объект был выведен интерактив ной оболочкой автоматически (как и в большинстве языков, 2 * 4 в языке Python равно 8), однако, если бы результат потребовался бы нам позднее, мы могли бы присвоить его переменной. Например: >>> x = times(3.14, 4) # Сохранить объект результата >>> x 12.56 Теперь посмотрим, что произойдет, если функции передать объекты совершенно разных типов: >>> times('Ni', 4) # Функции не имеют типа 'NiNiNiNi' На этот раз функция выполнила нечто совершенно иное. Вместо двух чисел в аргументах x и y функции были переданы строка и целое чис ло. Вспомните, что оператор * может работать как с числами, так и с последовательностями; поскольку в языке Python не требуется объ являть типы переменных, аргументов или возвращаемых значений, мы можем использовать функцию times для умножения чисел и повторения последовательностей. Другими словами, смысл функции times и тип возвращаемого значе ния определяются аргументами, которые ей передаются. Это основная 392 Глава 15. Основы функций идея языка Python (и, возможно, ключ к использованию языка), кото рую мы рассмотрим в следующем разделе. Полиморфизм в языке Python Как мы только что видели, смысл выражения x * y в нашей простой функции times полностью зависит от типов объектов x и y – одна и та же функция может выполнять умножение в одном случае и повторе ние в другом. В языке Python именно объекты определяют синтакси ческий смысл операции. В действительности оператор * – это всего лишь указание для обрабатываемых объектов. Такого рода зависимость от типов известна как полиморфизм – термин, впервые встретившийся нам в главе 4, который означает, что смысл опе рации зависит от типов обрабатываемых объектов. Поскольку Python – это язык с динамической типизацией, полиморфизм в нем проявляется повсюду. Фактически все операции в языке Python являются полимор фическими: вывод, извлечение элемента, оператор * и многие другие. Такое поведение заложено в язык изначально и объясняет в большой степени его краткость и гибкость. Например, единственная функция может автоматически применяться к целой категории типов объектов. Пока объекты поддерживают ожидаемый интерфейс (или протокол), функция сможет обрабатывать их. То есть, если объект, передаваемый функции, поддерживает ожидаемые методы и операторы выражений, он будет совместим с логикой функции. Даже в случае с нашей простой функцией это означает, что любые два объекта, поддерживающие оператор *, смогут обрабатываться функ цией, и неважно, что они из себя представляют и когда были созданы. Эта функция будет работать с числами (выполняя операцию умноже ния), с двумя строками или со строкой и числом (выполняя операцию повторения) и с любыми другими комбинациями объектов, поддержи вающими ожидаемый интерфейс – даже с объектами, порожденными на базе классов, которые мы еще пока не создали. Кроме того, если функции будут переданы объекты, которые не под держивают ожидаемый интерфейс, интерпретатор обнаружит ошибку при выполнении выражения * и автоматически возбудит исключение. Поэтому для нас совершенно бессмысленно предусматривать проверку на наличие ошибок в программном коде. Фактически, добавив такую проверку, мы ограничим область применения нашей функции, так как она сможет работать только с теми типами объектов, которые мы предусмотрели. Это важнейшее отличие философии языка Python от языков програм мирования со статической типизацией, таких как C++ и Java: про граммный код на языке Python не делает предположений о конкрет ных типах данных. В противном случае он сможет работать только с теми типами данных, которые ожидались на момент его написания, и он не будет поддерживать объекты других совместимых типов, кото Второй пример: пересечение последовательностей 393 рые могут быть созданы в будущем. Проверку типа объекта можно вы полнить с помощью таких средств, как встроенная функция type, но в этом случае программный код потеряет свою гибкость. Вообще гово ря, при программировании на языке Python во внимание принимают ся интерфейсы объектов, а не типы данных. Конечно, такая модель полиморфизма предполагает необходимость тестирования программного кода на наличие ошибок, так как изза от сутствия объявлений типов нет возможности с помощью компилятора выявить некоторые виды ошибок на ранней стадии. Однако в обмен на незначительное увеличение объема отладки мы получаем существен ное уменьшение объема программного кода, который требуется напи сать, и существенное увеличение его гибкости. На практике это озна чает чистую победу. Второй пример: пересечение последовательностей Рассмотрим второй пример функции, которая делает немного больше, чем простое умножение аргументов, и продолжает иллюстрацию ос нов функций. В главе 13 мы написали цикл for, который выбирал элементы, общие для двух строк. Там было замечено, что полезность этого программно го кода не так велика, как могла бы быть, потому что он может рабо тать только с определенными переменными и не может быть использо ван повторно. Безусловно, можно было бы просто скопировать этот блок кода и вставлять его везде, где потребуется, но такое решение нельзя признать ни удачным, ни универсальным – нам попрежнему придется редактировать каждую копию, изменяя имена последова тельностей; изменение алгоритма также влечет за собой необходи мость вносить изменения в каждую копию. Определение К настоящему моменту вы уже наверняка поняли, что решение этой дилеммы заключается в том, чтобы оформить этот цикл for в виде функции. Такой подход несет нам следующие преимущества: • Оформив программный код в виде функции, мы получаем возмож ность использовать его столько раз, сколько потребуется. • Так как вызывающая программа может передавать функции про извольные аргументы, функция сможет использоваться с любыми двумя последовательностями (или итерируемыми объектами) для получения их пересечения. • Когда логика работы оформлена в виде функции, достаточно изме нить программный код всего в одном месте, чтобы изменить способ получения пересечения. 394 Глава 15. Основы функций • Поместив функцию в файл модуля, ее можно будет импортировать и использовать в любой программе на вашем компьютере. В результате программный код, обернутый в функцию, превращается в универсальную утилиту нахождения пересечения: def intersect(seq1, seq2): res = [] # Изначально пустой результат for x in seq1: # Обход последовательности seq1 if x in seq2: # Общий элемент? res.append(x) # Добавить в конец return res В том, чтобы преобразовать фрагмент кода из главы 13 в функцию, нет ничего сложного – мы просто вложили оригинальную реализацию в инструкцию def и присвоили имена объектам, с которыми она рабо тает. Поскольку эта функция возвращает результат, мы также добави ли инструкцию return, которая возвращает полученный объект ре зультата вызывающей программе. Вызов Прежде чем функцию можно будет вызвать, ее необходимо создать. Для этого нужно выполнить инструкцию def, либо введя ее в интерак тивной оболочке, либо поместив файл модуля и выполнив операцию импорта. Как только инструкция def будет выполнена, можно будет вызывать функцию и передать ей два объекта последовательностей в круглых скобках: >>> s1 = "SPAM" >>> s2 = "SCAM" >>> intersect(s1, s2) # Строки ['S', 'A', 'M'] В данном примере мы передали функции две строки и получили спи сок общих символов. Алгоритм работы функции можно выразить про стой фразой: «Для всех элементов первого аргумента, если этот эле мент присутствует и во втором аргументе, добавить его в конец резуль тата». Этот алгоритм на языке Python описывается немного короче, чем на естественном языке, но работает он точно так же. Еще о полиморфизме Как и любая другая функция в языке Python, функция intersect так же является полиморфной. То есть она может обрабатывать объекты произвольных типов при условии, что они поддерживают ожидаемый интерфейс: >>> x = intersect([1, 2, 3], (1, 4)) # Смешивание типов >>> x # Сохраненный объект с результатом [1] Второй пример: пересечение последовательностей 395 На этот раз функции были переданы объекты разных типов – список и кортеж – и это не помешало ей отыскивать общие элементы. Так как отсутствует необходимость предварительного объявления типов аргу ментов, функция intersect благополучно будет выполнять итерации по объектам последовательностей любых типов при условии, что они будут поддерживать ожидаемые интерфейсы. Для функции intersect это означает, что первый объект должен обла дать поддержкой циклов for, а второй – поддержкой оператора in, вы полняющего проверку на вхождение. Любые два объекта, отвечающие этим условиям, будут обработаны независимо от их типов, включая как сами последовательности, такие как строки и списки, так и любые итерируемые объекты, с которыми мы встречались в главе 13, вклю чая файлы, словари и даже объекты, созданные на основе классов и использующие перегрузку операторов (эту тему мы будем рассмат ривать в шестой части книги). 1 И снова, если функции передать объекты, которые не поддерживают эти интерфейсы (например, числа), интерпретатор автоматически об наружит несоответствие и возбудит исключение, т. е. именно то, что нам требуется, и это лучше, чем добавление явной проверки типов ар гументов. Отказываясь от реализации проверки типов и позволяя ин терпретатору самому обнаруживать несоответствия, мы тем самым уменьшаем объем программного кода и повышаем его гибкость. Локальные переменные Переменная res внутри функции intersect – это то, что в языке Python называется локальной переменной, – имя, которое доступно только программному коду внутри инструкции def и существует только во время выполнения функции. Фактически, любые имена, которым тем или иным способом были присвоены некоторые значения внутри функ ции, по умолчанию классифицируются как локальные переменные. 1 Два важных замечания. Первое: с технической точки зрения файл может использоваться только в качестве первого аргумента функции intersect, по тому что во время первого же применения оператора in файл будет проска нирован до конца. Например, такой вызов, как intersect(open('data1.txt'), ['line1\n', 'line2\n', 'line3\n']) , будет работать, но вызов intersect(open ('data1.txt), open('data2.txt')) – нет, за исключением случая, когда пер вый файл содержит единственную строку. Для настоящих последователь ностей итерационный контекст вызывает метод iter для получения итера тора, который всегда переустанавливается в начало последовательности, но операции открытия и чтения файла приводят к исчерпанию итератора. Второе: в случае с классами нам, возможно, пришлось бы использовать ме тод перегрузки операторов __iter__ или более старый __getitem__, которые будут описаны в главе 24, чтобы обеспечить поддержку ожидаемого итера ционного протокола. В этом случае мы можем определить, что означает по нятие итерации для наших данных. |