Дополнительные инструменты управления потоком выполнения
Помимо только что рассмотренного оператора while, в Python есть ещё несколько операторов, с которыми мы познакомимся в этой главе.
4.1. Операторы if
Возможно, наиболее известный тип оператора — это оператор if. Например:
>>> x = int(input("Please enter an integer: "))
Please enter an integer: 42
>>> if x < 0:
... x = 0
... print('Negative changed to zero')
... elif x == 0:
... print('Zero')
... elif x == 1:
... print('Single')
... else:
... print('More')
...
More
Частей elif может быть ноль или больше, а часть else необязательна. Ключевое слово «elif» — это сокращение от «else if», оно помогает избежать чрезмерной вложенности. Последовательность if … elif … elif … заменяет операторы switch или case, встречающиеся в других языках.
Если вы сравниваете одно и то же значение с несколькими константами или проверяете конкретные типы или атрибуты, вам также может пригодиться оператор match. Подробнее см. в разделе Операторы match.
4.2. Операторы for
Оператор for в Python немного отличается от того, к чему вы могли привыкнуть в C или Pascal. Вместо того чтобы всегда перебирать арифметическую последовательность чисел (как в Pascal) или предоставлять пользователю возможность задавать шаг итерации и условие остановки (как в C), оператор for в Python перебирает элементы любой последовательности (списка или строки) в том порядке, в котором они в ней расположены. Например (без каламбура):
>>> # Measure some strings: >>> words = ['cat', 'window', 'defenestrate'] >>> for w in words: ... print(w, len(w)) ... cat 3 window 6 defenestrate 12
Код, изменяющий коллекцию во время перебора этой же коллекции, может быть непросто написать правильно. Обычно гораздо проще перебирать копию коллекции или создать новую коллекцию:
# Create a sample collection
users = {'Hans': 'active', 'Éléonore': 'inactive', '景太郎': 'active'}
# Strategy: Iterate over a copy
for user, status in users.copy().items():
if status == 'inactive':
del users[user]
# Strategy: Create a new collection
active_users = {}
for user, status in users.items():
if status == 'active':
active_users[user] = status
4.3. Функция range()
Если вам нужно перебрать последовательность чисел, пригодится встроенная функция range(). Она создаёт арифметические прогрессии:
>>> for i in range(5): ... print(i) ... 0 1 2 3 4
Указанная конечная точка не входит в созданную последовательность; range(10) создаёт 10 значений — допустимые индексы элементов последовательности длиной 10. Можно задать другое начальное число или указать иной шаг (в том числе отрицательный; иногда его называют «шагом»):
>>> list(range(5, 10)) [5, 6, 7, 8, 9] >>> list(range(0, 10, 3)) [0, 3, 6, 9] >>> list(range(-10, -100, -30)) [-10, -40, -70]
Чтобы перебрать индексы последовательности, можно объединить range() и len() следующим образом:
>>> a = ['Mary', 'had', 'a', 'little', 'lamb'] >>> for i in range(len(a)): ... print(i, a[i]) ... 0 Mary 1 had 2 a 3 little 4 lamb
Однако в большинстве подобных случаев удобнее использовать функцию enumerate(); см. раздел Приёмы перебора.
Если просто вывести объект range, произойдёт кое-что странное:
>>> range(10) range(0, 10)
Во многом объект, возвращаемый range(), ведёт себя как список, но на самом деле списком не является. Это объект, который при переборе возвращает по очереди элементы нужной последовательности, но при этом не создаёт сам список, экономя тем самым память.
Мы называем такой объект итерируемым, то есть подходящим для функций и конструкций, которые ожидают объект, из которого можно получать элементы по очереди, пока они не закончатся. Мы уже видели, что оператор for — такая конструкция, а пример функции, принимающей итерируемый объект, — sum():
>>> sum(range(4)) # 0 + 1 + 2 + 3 6
Позже мы рассмотрим другие функции, которые возвращают итерируемые объекты и принимают их в качестве аргументов. В главе Структуры данных мы подробнее обсудим list().
4.4. Операторы break и continue
Оператор break прерывает выполнение самого внутреннего охватывающего цикла for или while:
>>> for n in range(2, 10):
... for x in range(2, n):
... if n % x == 0:
... print(f"{n} equals {x} * {n//x}")
... break
...
4 equals 2 * 2
6 equals 2 * 3
8 equals 2 * 4
9 equals 3 * 3
Оператор continue переходит к следующей итерации цикла:
>>> for num in range(2, 10):
... if num % 2 == 0:
... print(f"Found an even number {num}")
... continue
... print(f"Found an odd number {num}")
...
Found an even number 2
Found an odd number 3
Found an even number 4
Found an odd number 5
Found an even number 6
Found an odd number 7
Found an even number 8
Found an odd number 9
4.5. Части else в циклах
В цикле for или while оператор break может сочетаться с частью else. Если цикл завершается, не выполнив break, выполняется часть else.
В цикле for часть else выполняется после завершения последней итерации цикла, то есть если не произошло прерывание.
В цикле while она выполняется после того, как условие цикла становится ложным.
В обоих типах циклов часть else не выполняется, если цикл был прерван оператором break. Разумеется, другие способы досрочного завершения цикла, например оператор return или вызванное исключение, также пропустят выполнение части else.
Это показано в следующем цикле for, который ищет простые числа:
>>> for n in range(2, 10): ... for x in range(2, n): ... if n % x == 0: ... print(n, 'equals', x, '*', n//x) ... break ... else: ... # loop fell through without finding a factor ... print(n, 'is a prime number') ... 2 is a prime number 3 is a prime number 4 equals 2 * 2 5 is a prime number 6 equals 2 * 3 7 is a prime number 8 equals 2 * 4 9 equals 3 * 3
(Да, код написан правильно. Присмотритесь: часть else относится к циклу for, а не к оператору if.)
Можно представить, что часть else связана с оператором if внутри цикла. По мере выполнения цикл будет проходить последовательность вида if/if/if/else. Оператор if находится внутри цикла и встречается несколько раз. Если условие хотя бы раз окажется истинным, произойдёт break. Если условие ни разу не окажется истинным, выполнится часть else за пределами цикла.
В цикле часть else больше похожа на часть else оператора try, чем на часть операторов if: часть else оператора try выполняется, если исключение не возникло, а часть else цикла — если не произошло break. Подробнее об операторе try и исключениях см. в разделе Обработка исключений.
4.6. Операторы pass
Оператор pass ничего не делает. Его можно использовать, когда синтаксис требует оператор, но программе не нужно выполнять никаких действий. Например:
>>> while True: ... pass # Busy-wait for keyboard interrupt (Ctrl+C) ...
Его часто используют для создания минимальных классов:
>>> class MyEmptyClass: ... pass ...
Ещё одно применение pass — использование его в качестве заполнителя для тела функции или условного оператора при работе над новым кодом. Это позволяет продолжать рассуждать на более абстрактном уровне. Оператор pass просто игнорируется:
>>> def initlog(*args): ... pass # Remember to implement this! ...
В последнем случае многие используют вместо pass литерал многоточия .... Для Python это использование не имеет особого значения и не является частью определения языка (здесь можно использовать любое константное выражение), но ... также принято использовать в качестве тела-заполнителя. См. раздел Объект Ellipsis.
4.7. Операторы match
Оператор match получает выражение и сравнивает его значение с последовательностью шаблонов, заданных одним или несколькими блоками case. На первый взгляд он похож на оператор switch в C, Java или JavaScript (и многих других языках), но больше напоминает сопоставление с образцом в таких языках, как Rust или Haskell. Выполняется только первый совпавший шаблон; он также может извлекать компоненты (элементы последовательности или атрибуты объекта) из значения в переменные. Если ни один шаблон не совпадает, ни одна из ветвей не выполняется.
В простейшем случае значение объекта сопоставляется с одним или несколькими литералами:
def http_error(status):
match status:
case 400:
return "Bad request"
case 404:
return "Not found"
case 418:
return "I'm a teapot"
case _:
return "Something's wrong with the internet"
Обратите внимание на последний блок: «имя переменной» _ выступает в роли подстановочного знака и совпадает всегда.
Можно объединить несколько литералов в один шаблон с помощью | («или»):
case 401 | 403 | 404:
return "Not allowed"
Шаблоны могут выглядеть как инструкции распаковки и использоваться для связывания переменных:
# point is an (x, y) tuple
match point:
case (0, 0):
print("Origin")
case (0, y):
print(f"Y={y}")
case (x, 0):
print(f"X={x}")
case (x, y):
print(f"X={x}, Y={y}")
case _:
raise ValueError("Not a point")
Внимательно изучите этот пример! Первый шаблон содержит два литерала и его можно рассматривать как расширение показанного выше шаблона с литералами. Но следующие два шаблона сочетают литерал и переменную, а переменная связывается со значением объекта (point). Четвёртый шаблон захватывает два значения, что делает его концептуально похожим на инструкцию распаковки (x, y) = point.
Если вы используете классы для структурирования данных, можно указать имя класса, за которым следует список аргументов, похожий на вызов конструктора, но позволяющий захватывать атрибуты в переменные:
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
def where_is(point):
match point:
case Point(x=0, y=0):
print("Origin")
case Point(x=0, y=y):
print(f"Y={y}")
case Point(x=x, y=0):
print(f"X={x}")
case Point():
print("Somewhere else")
case _:
print("Not a point")
Для некоторых встроенных классов, определяющих порядок своих атрибутов (например, dataclasses), можно использовать позиционные параметры. Также можно задать определённый порядок атрибутов в шаблонах, установив специальный атрибут __match_args__ в классах. Если ему присвоено значение («x», «y»), следующие шаблоны эквивалентны (и все связывают атрибут y с переменной var):
Point(1, var) Point(1, y=var) Point(x=1, y=var) Point(y=var, x=1)
Рекомендуется читать шаблоны как расширенный вариант того, что можно было бы написать слева от знака присваивания, чтобы понять, каким переменным какие значения будут присвоены. Оператор match присваивает значения только отдельным именам (например, var выше). Составные имена с точками (например, foo.bar), имена атрибутов (x= и y= выше) или имена классов (распознаваемые по следующим за ними скобкам «(…)», как Point выше) никогда не становятся целями присваивания.
Шаблоны могут быть сколь угодно вложенными. Например, если у нас есть короткий список точек и добавлена __match_args__, его можно сопоставить следующим образом:
class Point:
__match_args__ = ('x', 'y')
def __init__(self, x, y):
self.x = x
self.y = y
match points:
case []:
print("No points")
case [Point(0, 0)]:
print("The origin")
case [Point(x, y)]:
print(f"Single point {x}, {y}")
case [Point(0, y1), Point(0, y2)]:
print(f"Two on the Y axis at {y1}, {y2}")
case _:
print("Something else")
К шаблону можно добавить часть if, называемую «защитным условием». Если это условие ложно, match продолжает проверять следующий блок case. Обратите внимание: захват значений происходит до вычисления защитного условия:
match point:
case Point(x, y) if x == y:
print(f"Y=X at {x}")
case Point(x, y):
print(f"Not on the diagonal")
Ещё несколько важных особенностей этого оператора:
- Как и при распаковке, шаблоны кортежей и списков имеют совершенно одинаковый смысл и фактически сопоставляются с произвольными последовательностями. Важное исключение: они не сопоставляются с итераторами или строками.
- Шаблоны последовательностей поддерживают расширенную распаковку:
[x, y, *rest]и(x, y, *rest)работают аналогично распаковке. Имя после*также может быть_, поэтому(x, y, *_)соответствует последовательности как минимум из двух элементов, не связывая оставшиеся элементы. - Шаблоны отображений:
{"bandwidth": b, "latency": l}захватывает значения"bandwidth"и"latency"из словаря. В отличие от шаблонов последовательностей, дополнительные ключи игнорируются. Также поддерживается распаковка вида**rest. (Но**_было бы избыточным, поэтому это не разрешено.) -
Подшаблоны можно захватывать с помощью ключевого слова
as:case (Point(x1, y1), Point(x2, y2) as p2): ...
захватит второй элемент входных данных в
p2(если входные данные представляют собой последовательность из двух точек) - Большинство литералов сравниваются на равенство, однако одиночные значения
True,FalseиNoneсравниваются по идентичности. -
В шаблонах можно использовать именованные константы. Чтобы их не интерпретировали как переменные захвата, их имена должны содержать точки:
from enum import Enum class Color(Enum): RED = 'red' GREEN = 'green' BLUE = 'blue' color = Color(input("Enter your choice of 'red', 'blue' or 'green': ")) match color: case Color.RED: print("I see red!") case Color.GREEN: print("Grass is green") case Color.BLUE: print("I'm feeling the blues :(")
Более подробное объяснение и дополнительные примеры можно найти в документе PEP 636, написанном в виде учебного пособия.
4.8. Определение функций
Мы можем создать функцию, которая выводит числа Фибоначчи до произвольного предела:
>>> def fib(n): # write Fibonacci series less than n ... """Print a Fibonacci series less than n.""" ... a, b = 0, 1 ... while a < n: ... print(a, end=' ') ... a, b = b, a+b ... print() ... >>> # Now call the function we just defined: >>> fib(2000) 0 1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 1597
Ключевое слово def начинает определение функции. За ним должны следовать имя функции и список формальных параметров в круглых скобках. Инструкции, составляющие тело функции, начинаются со следующей строки и должны иметь отступ.
Первой инструкцией тела функции может быть строковый литерал; такой строковый литерал называется строкой документации функции, или docstring. (Подробнее о строках документации см. в разделе Строки документации.) Существуют инструменты, которые используют строки документации для автоматического создания документации в интернете или печатной документации, а также для интерактивного просмотра кода пользователем. Рекомендуется добавлять строки документации в написанный вами код — возьмите это за привычку.
Выполнение функции создаёт новую таблицу символов для локальных переменных функции. Точнее, все присваивания переменным в функции сохраняют значения в локальной таблице символов; при этом при обращении к переменной сначала выполняется поиск в локальной таблице символов, затем в таблицах символов охватывающих функций, затем в глобальной таблице символов и, наконец, в таблице встроенных имён. Поэтому глобальным переменным и переменным охватывающих функций нельзя напрямую присваивать значения внутри функции (если только глобальные переменные не указаны в операторе global, а переменные охватывающих функций — в операторе nonlocal), хотя к ним можно обращаться.
Фактические параметры (аргументы) вызова функции добавляются в локальную таблицу символов вызываемой функции при её вызове; таким образом, аргументы передаются по значению (где значение всегда является ссылкой на объект, а не значением самого объекта). [1] При вызове одной функции из другой или при рекурсивном вызове функции для этого вызова создаётся новая локальная таблица символов.
Определение функции связывает её имя с объектом функции в текущей таблице символов. Интерпретатор распознаёт объект, на который указывает это имя, как функцию, определённую пользователем. На тот же объект функции могут указывать и другие имена, с помощью которых к ней также можно обращаться:
>>> fib <function fib at 10042ed0> >>> f = fib >>> f(100) 0 1 1 2 3 5 8 13 21 34 55 89
Если вы знакомы с другими языками, вы можете возразить, что fib — это не функция, а процедура, поскольку она не возвращает значение. На самом деле даже функции без оператора return возвращают значение, хотя и довольно бесполезное. Это значение называется None (это встроенное имя). Интерпретатор обычно не выводит значение None, если бы оно было единственным выводимым значением. Если вам всё же нужно его увидеть, используйте print():
>>> fib(0) >>> print(fib(0)) None
Нетрудно написать функцию, которая возвращает список чисел Фибоначчи вместо их вывода:
>>> def fib2(n): # return Fibonacci series up to n ... """Return a list containing the Fibonacci series up to n.""" ... result = [] ... a, b = 0, 1 ... while a < n: ... result.append(a) # see below ... a, b = b, a+b ... return result ... >>> f100 = fib2(100) # call it >>> f100 # write the result [0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89]
Этот пример, как обычно, демонстрирует некоторые новые возможности Python:
- Оператор
returnвозвращает значение из функции.returnбез аргумента-выражения возвращаетNone. Если выполнение функции доходит до конца, она также возвращаетNone. - Инструкция
result.append(a)вызывает метод объекта спискаresult. Метод — это функция, которая «принадлежит» объекту и вызывается в формеobj.methodname, гдеobj— некоторый объект (это может быть выражение), аmethodname— имя метода, определённого типом объекта. Разные типы определяют разные методы. Методы разных типов могут иметь одинаковые имена, не вызывая неоднозначности. (Можно определять собственные типы объектов и методы с помощью классов; см. раздел Классы.) Показанный в примере методappend()определён для объектов-списков и добавляет новый элемент в конец списка. В этом примере он эквивалентенresult = result + [a], но работает эффективнее.
4.9. Подробнее об определении функций
Также можно определять функции с переменным числом аргументов. Существует три формы, которые можно комбинировать.
4.9.1. Значения аргументов по умолчанию
Наиболее полезная форма — указать значение по умолчанию для одного или нескольких аргументов. Это позволяет создать функцию, которую можно вызывать с меньшим числом аргументов, чем допускает её определение. Например:
def ask_ok(prompt, retries=4, reminder='Please try again!'):
while True:
reply = input(prompt)
if reply in {'y', 'ye', 'yes'}:
return True
if reply in {'n', 'no', 'nop', 'nope'}:
return False
retries = retries - 1
if retries < 0:
raise ValueError('invalid user response')
print(reminder)
Эту функцию можно вызвать несколькими способами:
- передав только обязательный аргумент:
ask_ok('Do you really want to quit?') - передав один из необязательных аргументов:
ask_ok('OK to overwrite the file?', 2) - или даже передав все аргументы:
ask_ok('OK to overwrite the file?', 2, 'Come on, only yes or no!')
В этом примере также вводится ключевое слово in. Оно проверяет, содержит ли последовательность заданное значение.
Значения по умолчанию вычисляются в момент определения функции в области видимости, где она определена, поэтому
i = 5
def f(arg=i):
print(arg)
i = 6
f()
выведет 5.
Важное предупреждение: Значение по умолчанию вычисляется только один раз. Это имеет значение, если значение по умолчанию — изменяемый объект, например список, словарь или экземпляр большинства классов. Например, следующая функция накапливает переданные ей аргументы при последующих вызовах:
def f(a, L=[]):
L.append(a)
return L
print(f(1))
print(f(2))
print(f(3))
Она выведет
[1] [1, 2] [1, 2, 3]
Если вы не хотите, чтобы значение по умолчанию использовалось совместно при последующих вызовах, можно вместо этого записать функцию так:
def f(a, L=None):
if L is None:
L = []
L.append(a)
return L
4.9.2. Именованные аргументы
Функции также можно вызывать с помощью именованных аргументов вида kwarg=value. Например, следующая функция:
def parrot(voltage, state='a stiff', action='voom', type='Norwegian Blue'):
print("-- This parrot wouldn't", action, end=' ')
print("if you put", voltage, "volts through it.")
print("-- Lovely plumage, the", type)
print("-- It's", state, "!")
принимает один обязательный аргумент (voltage) и три необязательных аргумента (state, action и type). Эту функцию можно вызвать любым из следующих способов:
parrot(1000) # 1 positional argument
parrot(voltage=1000) # 1 keyword argument
parrot(voltage=1000000, action='VOOOOOM') # 2 keyword arguments
parrot(action='VOOOOOM', voltage=1000000) # 2 keyword arguments
parrot('a million', 'bereft of life', 'jump') # 3 positional arguments
parrot('a thousand', state='pushing up the daisies') # 1 positional, 1 keyword
но все следующие вызовы будут недопустимы:
parrot() # required argument missing parrot(voltage=5.0, 'dead') # non-keyword argument after a keyword argument parrot(110, voltage=220) # duplicate value for the same argument parrot(actor='John Cleese') # unknown keyword argument
В вызове функции именованные аргументы должны следовать за позиционными аргументами. Все передаваемые именованные аргументы должны соответствовать одному из аргументов, принимаемых функцией (например, actor — недопустимый аргумент для функции parrot), а их порядок не имеет значения. Это относится и к обязательным аргументам (например, parrot(voltage=1000) тоже допустим). Ни один аргумент не может получить значение более одного раза. Вот пример вызова, который завершается ошибкой из-за этого ограничения:
>>> def function(a): ... pass ... >>> function(0, a=0) Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: function() got multiple values for argument 'a'
Если последний формальный параметр имеет вид **name, он получает словарь (см. Типы отображения — dict), содержащий все именованные аргументы, кроме соответствующих формальным параметрам. Его можно сочетать с формальным параметром вида *name (описанным в следующем подразделе), который получает кортеж с позиционными аргументами, следующими за списком формальных параметров. (*name должен идти перед **name.) Например, если определить функцию следующим образом:
def cheeseshop(kind, *arguments, **keywords):
print("-- Do you have any", kind, "?")
print("-- I'm sorry, we're all out of", kind)
for arg in arguments:
print(arg)
print("-" * 40)
for kw in keywords:
print(kw, ":", keywords[kw])
Её можно вызвать так:
cheeseshop("Limburger", "It's very runny, sir.",
"It's really very, VERY runny, sir.",
shopkeeper="Michael Palin",
client="John Cleese",
sketch="Cheese Shop Sketch")
и, конечно, она выведет:
-- Do you have any Limburger ? -- I'm sorry, we're all out of Limburger It's very runny, sir. It's really very, VERY runny, sir. ---------------------------------------- shopkeeper : Michael Palin client : John Cleese sketch : Cheese Shop Sketch
Обратите внимание: порядок вывода именованных аргументов гарантированно совпадает с порядком, в котором они были указаны при вызове функции.
4.9.3. Специальные параметры
По умолчанию аргументы можно передавать функции Python либо по позиции, либо явно указывая имя аргумента. Для удобства чтения и повышения производительности имеет смысл ограничить способы передачи аргументов, чтобы разработчику достаточно было посмотреть на определение функции и понять, передаются ли аргументы по позиции, по позиции или по имени либо только по имени.
Определение функции может выглядеть так:
def f(pos1, pos2, /, pos_or_kwd, *, kwd1, kwd2):
----------- ---------- ----------
| | |
| Positional or keyword |
| - Keyword only
-- Positional only
где / и * необязательны. Если они используются, эти символы указывают на тип параметра, определяющий, как аргументы можно передавать функции: только позиционно, позиционно или по имени и только по имени. Именованные параметры также называют параметрами с именами.
4.9.3.1. Позиционные или именованные аргументы
Если / и * отсутствуют в определении функции, аргументы можно передавать функции по позиции или по имени.
4.9.3.2. Только позиционные параметры
Если рассмотреть это подробнее, некоторые параметры можно пометить как только позиционные. Для таких параметров важен порядок, и их нельзя передавать по имени. Позиционные параметры размещаются перед / (косой чертой). Символ / логически отделяет позиционные параметры от остальных. Если в определении функции нет /, в ней нет позиционных параметров.
Параметры после / могут быть позиционными или именованными либо только именованными.
4.9.3.3. Только именованные аргументы
Чтобы пометить параметры как только именованные, то есть указать, что их нужно передавать по имени, поместите * в список аргументов непосредственно перед первым параметром, доступным только по имени.
4.9.3.4. Примеры функций
Рассмотрим следующие определения функций, уделив особое внимание маркерам / и *:
>>> def standard_arg(arg): ... print(arg) ... >>> def pos_only_arg(arg, /): ... print(arg) ... >>> def kwd_only_arg(*, arg): ... print(arg) ... >>> def combined_example(pos_only, /, standard, *, kwd_only): ... print(pos_only, standard, kwd_only)
В первом определении функции, standard_arg, используется наиболее знакомая форма: способ вызова не ограничен, а аргументы можно передавать по позиции или по имени:
>>> standard_arg(2) 2 >>> standard_arg(arg=2) 2
Вторая функция, pos_only_arg, принимает только позиционные параметры, поскольку в определении функции есть /:
>>> pos_only_arg(1) 1 >>> pos_only_arg(arg=1) Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: pos_only_arg() got some positional-only arguments passed as keyword arguments: 'arg'
Третья функция, kwd_only_arg, принимает только именованные аргументы, на что указывает * в определении функции:
>>> kwd_only_arg(3) Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: kwd_only_arg() takes 0 positional arguments but 1 was given >>> kwd_only_arg(arg=3) 3
В последнем примере в одном определении функции используются все три способа вызова:
>>> combined_example(1, 2, 3) Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: combined_example() takes 2 positional arguments but 3 were given >>> combined_example(1, 2, kwd_only=3) 1 2 3 >>> combined_example(1, standard=2, kwd_only=3) 1 2 3 >>> combined_example(pos_only=1, standard=2, kwd_only=3) Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: combined_example() got some positional-only arguments passed as keyword arguments: 'pos_only'
Наконец, рассмотрим определение функции, в котором может возникнуть конфликт между позиционным аргументом name и **kwds, у которого ключом является name:
def foo(name, **kwds):
return 'name' in kwds
Не существует вызова, который вернул бы True, поскольку именованный аргумент 'name' всегда будет связан с первым параметром. Например:
>>> foo(1, **{'name': 2})
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: foo() got multiple values for argument 'name'
>>>
Но с помощью / (только позиционных аргументов) это возможно, поскольку такой способ позволяет передать name как позиционный аргумент, а 'name' — как ключ именованного аргумента:
>>> def foo(name, /, **kwds):
... return 'name' in kwds
...
>>> foo(1, **{'name': 2})
True
Иными словами, имена позиционных параметров можно использовать в **kwds без неоднозначности.
4.9.3.5. Краткое повторение
Выбор параметров в определении функции зависит от конкретного случая:
def f(pos1, pos2, /, pos_or_kwd, *, kwd1, kwd2):
Рекомендации:
- Используйте только позиционные параметры, если не хотите раскрывать пользователю имена параметров. Это полезно, когда имена параметров не имеют реального значения, когда нужно обеспечить определённый порядок аргументов при вызове функции или когда нужно принимать некоторые позиционные параметры и произвольные именованные аргументы.
- Используйте только именованные параметры, если их имена имеют значение и явное указание имён делает определение функции понятнее либо если вы хотите, чтобы пользователи не полагались на позицию передаваемого аргумента.
- В API используйте только позиционные параметры, чтобы избежать нарушающих совместимость изменений API в случае переименования параметра в будущем.
4.9.4. Произвольное число аргументов
Наконец, наименее часто используемый вариант — указать, что функцию можно вызывать с произвольным числом аргументов. Эти аргументы будут собраны в кортеж (см. Кортежи и последовательности). Перед переменным числом аргументов может находиться ноль или более обычных аргументов.
def write_multiple_items(file, separator, *args):
file.write(separator.join(args))
Обычно эти вариативные аргументы располагаются последними в списке формальных параметров, поскольку они собирают все оставшиеся входные аргументы, переданные функции. Любые формальные параметры после параметра *args являются аргументами, доступными только по имени, то есть их можно использовать только как именованные, а не позиционные аргументы.
>>> def concat(*args, sep="/"):
... return sep.join(args)
...
>>> concat("earth", "mars", "venus")
'earth/mars/venus'
>>> concat("earth", "mars", "venus", sep=".")
'earth.mars.venus'
4.9.5. Распаковка списков аргументов
Обратная ситуация возникает, когда аргументы уже находятся в списке или кортеже, но их нужно распаковать для вызова функции, которой требуются отдельные позиционные аргументы. Например, встроенная функция range() принимает отдельные аргументы start и stop. Если они недоступны по отдельности, добавьте к вызову функции оператор *, чтобы распаковать аргументы из списка или кортежа:
>>> list(range(3, 6)) # normal call with separate arguments [3, 4, 5] >>> args = [3, 6] >>> list(range(*args)) # call with arguments unpacked from a list [3, 4, 5]
Аналогичным образом словари могут передавать именованные аргументы с помощью оператора **:
>>> def parrot(voltage, state='a stiff', action='voom'):
... print("-- This parrot wouldn't", action, end=' ')
... print("if you put", voltage, "volts through it.", end=' ')
... print("E's", state, "!")
...
>>> d = {"voltage": "four million", "state": "bleedin' demised", "action": "VOOM"}
>>> parrot(**d)
-- This parrot wouldn't VOOM if you put four million volts through it. E's bleedin' demised !
4.9.6. Лямбда-выражения
Небольшие анонимные функции можно создавать с помощью ключевого слова lambda. Эта функция возвращает сумму двух своих аргументов: lambda a, b: a+b. Лямбда-функции можно использовать везде, где требуются объекты-функции. Синтаксически они ограничены одним выражением. По смыслу это лишь синтаксический сахар для обычного определения функции. Как и вложенные определения функций, лямбда-функции могут обращаться к переменным из внешней области видимости:
>>> def make_incrementor(n): ... return lambda x: x + n ... >>> f = make_incrementor(42) >>> f(0) 42 >>> f(1) 43
В примере выше лямбда-выражение используется для возврата функции. Ещё один вариант применения — передача небольшой функции в качестве аргумента. Например, list.sort() принимает функцию сортировки key, которой может быть лямбда-функция:
>>> pairs = [(1, 'one'), (2, 'two'), (3, 'three'), (4, 'four')] >>> pairs.sort(key=lambda pair: pair[1]) >>> pairs [(4, 'four'), (1, 'one'), (3, 'three'), (2, 'two')]
4.9.7. Строки документации
Ниже приведены некоторые правила оформления содержимого и форматирования строк документации.
Первая строка всегда должна содержать краткое и ёмкое описание назначения объекта. Для краткости не следует явно указывать имя или тип объекта, поскольку их можно определить другими способами (кроме случаев, когда имя является глаголом, описывающим действие функции). Эта строка должна начинаться с заглавной буквы и заканчиваться точкой.
Если строка документации состоит из нескольких строк, вторая строка должна быть пустой, визуально отделяя краткое описание от остального текста. В следующих строках следует разместить один или несколько абзацев с описанием способов вызова объекта, его побочных эффектов и т. д.
Парсер Python удаляет отступы из многострочных строковых литералов, используемых в качестве строк документации модулей, классов или функций.
Вот пример многострочной строки документации:
>>> def my_function():
... """Do nothing, but document it.
...
... No, really, it doesn't do anything:
...
... >>> my_function()
... >>>
... """
... pass
...
>>> print(my_function.__doc__)
Do nothing, but document it.
No, really, it doesn't do anything:
>>> my_function()
>>>
4.9.8. Аннотации функций
Аннотации функций — это полностью необязательные метаданные о типах, используемых в функциях, определённых пользователем (подробнее см. PEP 3107 и PEP 484).
Аннотации хранятся в атрибуте __annotations__ функции в виде словаря и не влияют ни на какие другие её части. Аннотации параметров задаются двоеточием после имени параметра, за которым следует выражение, вычисляемое в значение аннотации. Аннотации возвращаемого значения задаются литералом ->, за которым следует выражение; они располагаются между списком параметров и двоеточием, обозначающим конец инструкции def. В следующем примере аннотированы обязательный аргумент, необязательный аргумент и возвращаемое значение:
>>> def f(ham: str, eggs: str = 'eggs') -> str:
... print("Annotations:", f.__annotations__)
... print("Arguments:", ham, eggs)
... return ham + ' and ' + eggs
...
>>> f('spam')
Annotations: {'ham': <class 'str'>, 'return': <class 'str'>, 'eggs': <class 'str'>}
Arguments: spam eggs
'spam and eggs'
4.10. Вставка: стиль написания кода
Теперь, когда вы собираетесь писать более длинные и сложные программы на Python, самое время поговорить о стиле написания кода. Большинство языков можно записывать (или, точнее, форматировать) в разных стилях; одни из них читаются легче других. Всегда полезно облегчить другим чтение вашего кода, и хороший стиль написания кода в этом очень помогает.
Для Python сложился свод правил PEP 8, которого придерживается большинство проектов; он способствует созданию легко читаемого и приятного для глаз кода. Каждому разработчику Python стоит прочитать его; вот основные правила:
-
Используйте отступы в 4 пробела, а не табуляцию.
Четыре пробела — хороший компромисс между небольшим отступом (позволяет глубже вкладывать конструкции) и большим отступом (облегчает чтение). Табуляция может вызвать путаницу, поэтому лучше её не использовать.
-
Ограничивайте длину строк 79 символами.
Это удобно для пользователей с небольшими экранами и позволяет размещать рядом несколько файлов с кодом на больших экранах.
- Отделяйте функции и классы пустыми строками, как и крупные блоки кода внутри функций.
- По возможности размещайте комментарии на отдельных строках.
- Используйте строки документации.
- Ставьте пробелы вокруг операторов и после запятых, но не непосредственно внутри скобок:
a = f(1, 2) + g(3, 4). - Последовательно именуйте классы и функции; для классов принято использовать
UpperCamelCase, а для функций и методов —lowercase_with_underscores. В качестве имени первого аргумента метода всегда используйтеself(подробнее о классах и методах см. в разделе Первое знакомство с классами). - Не используйте необычные кодировки, если ваш код предназначен для международного использования. В любом случае лучше всего подходят кодировка Python по умолчанию — UTF-8 — или даже обычная ASCII.
- Также не используйте символы, отличные от ASCII, в идентификаторах, если есть хоть малейшая вероятность, что код будут читать или сопровождать люди, говорящие на другом языке.
Сноски
© 2001 Python Software Foundation
Licensed under the PSF License.
https://docs.python.org/3.14/tutorial/controlflow.html