справочник по Python. мм isbn 9785932861578 9 785932 861578
Скачать 4.21 Mb.
|
Глава 6 . Функции и функциональное программирование В большинстве своем программы разбиты на функции – для достижения большей модульности и упрощения обслуживания. Язык Python не только облегчает объявление функций, но и снабжает их удивительным количе- ством дополнительных особенностей, унаследованных из функциональ- ных языков программирования. В этой главе описываются функции, пра- вила видимости, замыкания, декораторы, генераторы, сопрограммы и дру- гие особенности, присущие функциональному программированию. Кроме того, здесь же описываются генераторы списков и выражения-генераторы, которые являются мощными инструментами программирования в декла- ративном стиле и обработки данных. Функции Функции объявляются с помощью инструкции def: def add(x,y): return x + y Тело функции – это простая последовательность инструкций, которые выполняются при вызове функции. Вызов функции осуществляется как обращение к имени функции, за которым следует кортеж аргументов, на- пример: a = add(3,4). Порядок следования аргументов в вызове должен со- впадать с порядком следования аргументов в определении функции. Если будет обнаружено несоответствие, интерпретатор возбудит исключение TypeError Аргументы функции могут иметь значения по умолчанию. Например: def split(line,delimiter=’,’): инструкции Если в объявлении функции присутствует аргумент со значением по умол- чанию, этот аргумент и все следующие за ним считаются необязательны- Функции 131 ми. Если значения по умолчанию назначены не всем необязательным аргу- ментам, возбуждается исключение SyntaxError. Значения аргументов по умолчанию всегда связываются с теми самыми объектами, которые использовались в качестве значений в объявлении функции. Например: a = 10 def foo(x=a): return x ёё a = 5 # Изменить значение ‘a’. foo() # вернет 10 (значение по умолчанию не изменилось) При этом использование изменяемых объектов в качестве значений по умолчанию может приводить к неожиданным результатам: def foo(x, items=[]): items.append(x) return items ёё foo(1) # вернет [1] foo(2) # вернет [1, 2] foo(3) # вернет [1, 2, 3] Обратите внимание, что аргумент по умолчанию запоминает изменения в результате предыдущих вызовов. Чтобы предотвратить такое поведение, в качестве значения по умолчанию лучше использовать None и добавить проверку, как показано ниже: def foo(x, items=None): if items is None: items = [] items.append(x) return items Если перед последним аргументом в определении функции добавить сим- вол звездочки (*), функция сможет принимать переменное число аргумен- тов: def fprintf(file, fmt, *args): file.write(fmt % args) ёё # Вызов функции fprintf. # Аргумент args получит значение (42,”hello world”, 3.45) fprintf(out,”%d %s %f”, 42, “hello world”, 3.45) В данном случае все дополнительные аргументы будут помещены в пере- менную args в виде кортежа. Чтобы передать кортеж args другой функции, как если бы это был набор аргументов, достаточно использовать синтаксис *args , как показано ниже: def printf(fmt, *args): # Вызвать другую функцию и передать ей аргумент args fprintf(sys.stdout, fmt, *args) 132 Глава 6. Функции и функциональное программирование Кроме того, имеется возможность передавать функциям аргументы, явно указывая их имена и значения. Такие аргументы называются именован- ными аргументами . Например: def foo(w,x,y,z): инструкции ёё # Вызов с именованными аргументами foo(x=3, y=22, w=’hello’, z=[1,2]) При вызове с именованными аргументами порядок следования аргумен- тов не имеет значения. Однако при этом должны быть явно указаны име- на всех обязательных аргументов, если только они не имеют значений по умолчанию. Если пропустить какой-либо из обязательных аргументов или использовать именованный аргумент, не совпадающий ни с одним из имен параметров в определении функции, будет возбуждено исключение TypeEr- ror . Кроме того, так как в языке Python любая функция может быть вызва- Python любая функция может быть вызва- любая функция может быть вызва- на с использованием именованных аргументов, то резонно придерживать- ся правила – объявлять функции с описательными именами аргументов. В одном вызове функции допускается одновременно использовать позици- онные и именованные аргументы, при соблюдении следующих условий: первыми должны быть указаны позиционные аргументы, должны быть определены значения для всех обязательных аргументов и ни для одного из аргументов не должно быть передано более одного значения. Например: foo(‘hello’, 3, z=[1,2], y=22) foo(3, 22, w=’hello’, z=[1,2]) # TypeError. Несколько значений для w Если последний аргумент в объявлении функции начинается с символов **, все дополнительные именованные аргументы (имена которых отсутствуют в объявлении функции) будут помещены в словарь и переданы функции. Это обеспечивает удобную возможность писать функции, способные при- нимать значительное количество параметров, описание которых в объяв- лении функции выглядело бы слишком громоздко. Например: def make_table(data, **parms): # Получить параметры из аргумента parms (словарь) fgcolor = parms.pop(“fgcolor”,”black”) bgcolor = parms.pop(“bgcolor”,”white”) width = parms.pop(“width”,None) ... # Нет больше параметров if parms: raise TypeError(“Неподдерживаемые параметры %s” % list(parms)) ёё make_table(items, fgcolor=”black”, bgcolor=”white”, border=1, borderstyle=”grooved”, cellpadding=10, width=400) Допускается одновременно использовать дополнительные именованные аргументы и список позиционных аргументов переменной длины, при условии, что аргумент, начинающийся с символов **, указан последним: Передача параметров и возвращаемые значения 133 # Принимает переменное количество позиционных или именованных аргументов def spam(*args, **kwargs): # args – кортеж со значениями позиционных аргументов # kwargs – словарь с именованными аргументами ... Словарь с дополнительными именованными аргументами также можно передать другой функции, используя синтаксис **kwargs: def callfunc(*args, **kwargs): func(*args,**kwargs) Такой способ использования аргументов *args и **kwargs часто применяется при создании оберток для других функций. Например, функция callfunc() принимает любые комбинации аргументов и просто передает их функции func() Передача параметров и возвращаемые значения Параметры функции, которые передаются ей при вызове, являются обыч- ными именами, ссылающимися на входные объекты. Семантика переда- чи параметров в языке Python не имеет точного соответствия какому-либо одному способу, такому как «передача по значению» или «передача по ссылке», которые могут быть вам знакомы по другим языкам программи- рования. Например, если функции передается неизменяемое значение, это выглядит, как передача аргумента по значению. Однако при передаче из- меняемого объекта (такого как список или словарь), который модифици- руется функцией, эти изменения будут отражаться на исходном объекте. Например: a = [1, 2, 3, 4, 5] def square(items): for i,x in enumerate(items): items[i] = x * x # Изменяется элемент в самом списке ёё square(a) # Изменит содержимое списка: [1, 4, 9, 16, 25] Если функция изменяет значения аргументов или оказывает влияние на состояние других частей программы, про такие функции говорят, что они имеют побочные эффекты. В общем случае подобного стиля программиро- вания лучше избегать, потому что с ростом размеров или сложности про- граммы такие функции могут стать источником трудноуловимых ошибок (из инструкции вызова функции сложно определить, имеет ли она побоч- ные эффекты). Такие функции трудно приспособить для работы в составе многопоточных программ, потому что побочные эффекты обычно прихо- дится защищать блокировками. Инструкция return возвращает значение из функции. Если значение не указано или опущена сама инструкция return, вызывающей программе возвращается объект None. Чтобы вернуть несколько значений, достаточно поместить их в кортеж: 134 Глава 6. Функции и функциональное программирование def factor(a): d = 2 while (d <= (a / 2)): if ((a / d) * d == a): return ((a / d), d) d = d + 1 return (a, 1) Е сли функция возвращает несколько значений в виде кортежа, их можно присвоить сразу нескольким отдельным переменным: x, y = factor(1243) # Возвращаемые значения записываются в переменные x и y. или (x, y) = factor(1243) # Альтернативная версия. Результат тот же самый. Правила видимости При каждом вызове функции создается новое локальное пространство имен. Это пространство имен представляет локальное окружение, содер- жащее имена параметров функции, а также имена переменных, которым были присвоены значения в теле функции. Когда возникает необходимость отыскать имя, интерпретатор в первую очередь просматривает локальное пространство имен. Если искомое имя не было найдено, поиск продолжа- ется в глобальном пространстве имен. Глобальным пространством имен для функций всегда является пространство имен модуля, в котором эта функция была определена. Если интерпретатор не найдет искомое имя в глобальном пространстве имен, поиск будет продолжен во встроенном пространстве имен. Если и эта попытка окажется неудачной, будет возбуж- дено исключение NameError. Одна из необычных особенностей пространств имен заключается в работе с глобальными переменными внутри функции. Рассмотрим в качестве при- мера следующий фрагмент: a = 42 def foo(): a = 13 foo() # Переменная a по-прежнему имеет значение 42 После выполнения этого фрагмента переменная a по-прежнему будет иметь значение 42, несмотря на то что внутри функции foo значение переменной a изменяется. Когда внутри функции выполняется операция присваивания значения переменной, она всегда выполняется в локальном пространстве имен функции; в результате переменная a в теле функции ссылается на со- вершенно другой объект, содержащий значение 13, а не на тот, на который ссылается внешняя переменная. Обеспечить иное поведение можно с по- мощью инструкции global. Она просто объявляет принадлежность имен глобальному пространству имен; использовать ее необходимо, только когда потребуется изменить глобальную переменную. Ее можно поместить где- нибудь в теле функции и использовать неоднократно. Например: Правила видимости 135 a = 42 b = 37 def foo(): global a # переменная ‘a’ находится в глобальном пространстве имен a = 13 b = 0 foo() # теперь a имеет значение 13. b – по-прежнему имеет значение 37. В языке Python поддерживается возможность определять вложенные функ- Python поддерживается возможность определять вложенные функ- поддерживается возможность определять вложенные функ- ции. Например: def countdown(start): n = start def display(): # Объявление вложенной функции print(‘T-minus %d’ % n) while n > 0: display() n -= 1 Переменные во вложенных функциях привязаны к лексической области видимости . То есть поиск имени переменной начинается в локальной об- ласти видимости и затем последовательно продолжается во всех объемлю- щих областях видимости внешних функций, в направлении от внутрен- них к внешним. Если и в этих пространствах имен искомое имя не будет найдено, поиск будет продолжен в глобальном, а затем во встроенном про- странстве имен, как и прежде. Несмотря на доступность промежуточных вмещающих областей видимости, Python 2 позволяет выполнять присваи- Python 2 позволяет выполнять присваи- 2 позволяет выполнять присваи- вание только переменным, находящимся в самой внутренней (локальные переменные) и в самой внешней (при использовании инструкции global) области видимости. По этой причине вложенные функции не имеют воз- можности присваивать значения локальным переменным, определенным во внешних функциях. Например, следующий программный код не будет работать: def countdown(start): n = start def display(): print(‘T-minus %d’ % n) def decrement(): n -= 1 # Не будет работать в Python 2 while n > 0: display() decrement() В Python 2 эту проблему можно решить, поместив значения, которые пред- Python 2 эту проблему можно решить, поместив значения, которые пред- 2 эту проблему можно решить, поместив значения, которые пред- полагается изменять, в список или в словарь. В Python 3 можно объявить переменную n как nonlocal, например: def countdown(start): n = start def display(): print(‘T-minus %d’ % n) def decrement(): 136 Глава 6. Функции и функциональное программирование nonlocal n # Связывает имя с внешней переменной n (только в Python 3) n -= 1 while n > 0: display() decrement() Объявление nonlocal не может использоваться для привязки указанного имени к локальной переменной, определенной внутри одной из вложенных функций (то есть в динамической области видимости). Поэтому для тех, кто имеет опыт работы с языком Perl, замечу, что объявление nonlocal – это не то же самое, что объявление local в языке Perl. При обращении к локальной переменной до того, как ей будет присвоено значение, возбуждается исключение UnboundLocalError. Следующий пример демонстрирует один из возможных сценариев, когда такое исключение мо- жет возникнуть: i = 0 def foo(): i = i + 1 # Приведет к исключению UnboundLocalError print(i) В этой функции переменная i определяется как локальная (потому что внутри функции ей присваивается некоторое значение и отсутствует ин- струкция global). При этом инструкция присваивания i = i + 1 пытает- ся прочитать значение переменной i еще до того, как ей будет присвоено значение. Хотя в этом примере существует глобальная переменная i, она не используется для получения значения. Переменные в функциях могут быть либо локальными, либо глобальными и не могут произвольно изме- нять область видимости в середине функции. Например, нельзя считать, что переменная i в выражении i + 1 в предыдущем фрагменте обращается к глобальной переменной i; при этом переменная i в вызове print(i) подра- зумевает локальную переменную i, созданную в предыдущей инструкции. Функции как объекты и замыкания Функции в языке Python – объекты первого класса. Это означает, что они могут передаваться другим функциям в виде аргументов, сохраняться в структурах данных и возвращаться функциями в виде результата. Ниже приводится пример функции, которая на входе принимает другую функ- цию и вызывает ее: # foo.py def callf(func): return func() А это пример использования функции, объявленной выше: >>> import foo >>> def helloworld(): ... return ‘Привет, Мир!’ ... >>> foo.callf(helloworld) # Передача функции в виде аргумента Функции как объекты и замыкания 137 ‘Привет, Мир!’ >>> Когда функция интерпретируется как данные, она неявно несет инфор- мацию об окружении, в котором была объявлена функция, что оказывает влияние на связывание свободных переменных в функции. В качестве при- мера рассмотрим модифицированную версию файла foo.py, в который были добавлены переменные: # foo.py x = 42 def callf(func): return func() Теперь исследуем следующий пример: >>> import foo >>> x = 37 >>> def helloworld(): ... return “Привет, Мир! x = %d” % x ... >>> foo.callf(helloworld) # Передача функции в виде аргумента ‘Привет, Мир! x = 37’ >>> Обратите внимание, как функция helloworld() в этом примере использует значение переменной x, которая была определена в том же окружении, что и сама функция helloworld(). Однако хотя переменная x определена в файле foo.py и именно там фактически вызывается функция helloworld(), при ис- полнении функцией helloworld() используется не это значение переменной x. Когда инструкции, составляющие функцию, упаковываются вместе с окру- жением, в котором они выполняются, получившийся объект называют за- мыканием . Такое поведение предыдущего примера объясняется наличием у каждой функции атрибута __globals__, ссылающегося на глобальное про- странство имен, в котором функция была определена. Это пространство имен всегда соответствует модулю, в котором была объявлена функция. Для предыдущего примера атрибут __globals__ содержит следующее: >>> helloworld.__globals __ {‘__builtins__’: ‘helloworld’: ‘x’: 37, ‘__name__’: ‘__main__’, ‘__doc__’: None ‘foo’: >>> Когда функция используется как вложенная, в замыкание включается все ее окружение, необходимое для работы внутренней функции. Например: import foo def bar(): x = 13 def helloworld(): return “Привет, Мир! x = %d” % x foo.callf(helloworld) # вернет ‘Привет, Мир! x = 13’ 138 Глава 6. Функции и функциональное программирование Замыкания и вложенные функции особенно удобны, когда требуется на- писать программный код, реализующий концепцию отложенных вычис- лений. Рассмотрим еще один пример: from urllib import urlopen # from urllib.request import urlopen (Python 3) def page(url): def get(): return urlopen(url).read() return get Функция page() в этом примере не выполняет никаких вычислений. Она просто создает и возвращает функцию get(), которая при вызове будет из- влекать содержимое веб-страницы. То есть вычисления, которые произ- водятся в функции get(), в действительности откладываются до момента, когда фактически будет вызвана функция get(). Например: >>> python = page(“http://www.python.org”) >>> jython = page(“http://www.jython.org”) >>> python >>> jython >>> pydata = python() # Извлечет страницу http://www.python.org >>> jydata = jython() # Извлечет страницу http://www.jython.org >>> Две переменные, python и jython, объявленные в этом примере, в действи- тельности являются двумя различными версиями функции get(). Хотя функция page(), которая создала эти значения, больше не выполняется, тем не менее обе версии функции get() неявно несут в себе значения внешних переменных на момент создания функции get(). То есть при выполнении функция get() вызовет urlopen(url) со значением url, которое было передано функции page(). Взглянув на атрибуты объектов python и jython, можно уви- деть, какие значения переменных были включены в замыкания. Например: >>> |