Содержание страницы
4. Дополнительные средства управления потоком¶More Control Flow Tools
Помимо только что представленного оператора while, Python использует ещё несколько,
с которыми мы познакомимся в этой главе.
4.1. ifИнструкции¶if Statements
Пожалуй, наиболее известным типом оператора является оператор 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 Statements
Оператор for в Python немного отличается от того, к чему вы могли привыкнуть
в C или Pascal. Вместо того чтобы всегда выполнять итерацию по арифметической прогрессии
чисел (как в Pascal) или давать пользователю возможность задавать как шаг итерации, так и условие остановки (как в C), оператор for в Python
выполняет итерацию по элементам любой последовательности (списка или строки) в том порядке,
в котором они встречаются в последовательности. Например (без каламбура):
>>> # Измеряем некоторые строки:
>>> words = ['cat', 'window', 'defenestrate']
>>> for w in words:
... print(w, len(w))
...
cat 3
window 6
defenestrate 12
Код, изменяющий коллекцию во время итерации по той же коллекции, может быть сложным для правильной реализации. Вместо этого обычно проще выполнить цикл по копии коллекции или создать новую коллекцию:
# Создаём образец коллекции
users = {'Hans': 'active', 'Éléonore': 'inactive', '景太郎': 'active'}
# Стратегия: перебираем копию
for user, status in users.copy().items():
if status == 'inactive':
del users[user]
# Стратегия: создать новую коллекцию
active_users = {}
for user, status in users.items():
if status == 'active':
active_users[user] = status
4.3. Функция range()¶The range() Function
Если вам действительно нужно выполнить итерацию по последовательности чисел, встроенная функция
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 and continue Statements
Оператор 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 Предложения else в циклах¶else Clauses on Loops
В цикле 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:
... # цикл завершился без нахождения множителя
... 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 Statements
Инструкция pass ничего не делает. Она может использоваться, когда синтаксически
требуется инструкция, но программа не должна выполнять никаких действий. Например:
>>> while True:
... pass # Активное ожидание прерывания с клавиатуры (Ctrl+C)
...
Это часто используется для создания минимальных классов:
>>> class MyEmptyClass:
... pass
...
Ещё одно место, где можно использовать pass – в качестве заполнителя для тела функции или
условного оператора при работе над новым кодом, позволяя сохранять более абстрактный уровень
мышления. pass молча игнорируется:
>>> def initlog(*args):
... pass # Не забудьте реализовать это!
...
4.7. matchИнструкции¶match Statements
Оператор 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"
Обратите внимание на последний блок: «имя переменной» _ действует как подстановочный знак (wildcard) и совпадает всегда. Если ни один case не совпал, ни одна ветвь не выполняется.
Можно объединить несколько литералов в одном шаблоне с помощью | («или»):
case 401 | 403 | 404:
return "Not allowed"
Шаблоны могут выглядеть как распаковывающие присваивания и могут использоваться для связывания переменных:
# point – это кортеж (x, y)
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)
Рекомендуемый способ чтения шаблонов – рассматривать их как расширенную форму того, что
можно было бы поставить слева от присваивания, чтобы понять, какие переменные будут чему
присвоены.
Только отдельные имена (например, var выше) присваиваются инструкцией match.
Составные имена (например, 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. Определение функций¶Defining Functions
Можно создать функцию, которая выводит ряд Фибоначчи до произвольной границы:
>>> def fib(n): # вывести ряд Фибоначчи, меньший n
... """Вывести ряд Фибоначчи, меньший n."""
... a, b = 0, 1
... while a < n:
... print(a, end=' ')
... a, b = b, a+b
... print()
...
>>> # Теперь вызовите только что определённую функцию:
>>> fib(2000)
0 1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 1597
Ключевое слово def вводит определение функции. За ним должны следовать
имя функции и список формальных параметров в скобках. Инструкции, составляющие тело функции,
начинаются со следующей строки и должны иметь отступ.
Первый оператор тела функции может быть строковым литералом; этот строковый литерал является строкой документации функции, или docstring. (Подробнее о docstring можно найти в разделе Строки документации.) Существуют инструменты, которые используют docstring для автоматического создания онлайн- или печатной документации, или чтобы позволить пользователю интерактивно просматривать код; рекомендуется включать docstring в код, который вы пишете, так что сделайте это привычкой.
Выполнение функции вводит новую таблицу символов, используемую для локальных переменных функции. Точнее, все присваивания переменных в функции сохраняют значение в локальной таблице символов; тогда как обращения к переменным сначала ищут в локальной таблице символов, затем в локальных таблицах символов объемлющих функций, затем в глобальной таблице символов и, наконец, в таблице встроенных имён. Таким образом, глобальные переменные и переменные объемлющих функций не могут быть напрямую присвоены внутри функции (если только, для глобальных переменных, они не указаны в операторе global, или, для переменных объемлющих функций, в операторе nonlocal), хотя на них можно ссылаться.
The actual parameters (arguments) to a function call are introduced in the local symbol table of the called function when it is called; thus, arguments are passed using call by value (where the value is always an object reference, not the value of the object). [1] When a function calls another function, or calls itself recursively, a new local symbol table is created for that call.
Определение функции связывает имя функции с объектом функции в текущей таблице символов. Интерпретатор распознаёт объект, на который указывает это имя, как определённую пользователем функцию. Другие имена также могут указывать на тот же объект функции и использоваться для доступа к функции:
>>> 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): # возвращает ряд Фибоначчи до n
... """Возвращает список, содержащий ряд Фибоначчи до n."""
... result = []
... a, b = 0, 1
... while a < n:
... result.append(a) # см. ниже
... a, b = b, a+b
... return result
...
>>> f100 = fib2(100) # вызов
>>> f100 # вывод результата
[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. Подробнее об определении функций¶More on Defining Functions
Также можно определять функции с переменным числом аргументов. Существует три формы, которые можно комбинировать.
4.9.1. Значения аргументов по умолчанию¶Default Argument Values
Наиболее полезная форма – указать значение по умолчанию для одного или нескольких аргументов. Это создаёт функцию, которую можно вызывать с меньшим количеством аргументов, чем она определена допускать. Например:
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. Именованные аргументы¶Keyword Arguments
Функции также можно вызывать с помощью именованных аргументов вида 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 позиционный аргумент
parrot(voltage=1000) # 1 именованный аргумент
parrot(voltage=1000000, action='VOOOOOM') # 2 именованных аргумента
parrot(action='VOOOOOM', voltage=1000000) # 2 именованных аргумента
parrot('a million', 'bereft of life', 'jump') # 3 позиционных аргумента
parrot('a thousand', state='pushing up the daisies') # 1 позиционный, 1 именованный
но все следующие вызовы будут недопустимы:
parrot() # отсутствует обязательный аргумент
parrot(voltage=5.0, 'dead') # неименованный аргумент после именованного
parrot(110, voltage=220) # дублирование значения для одного и того же аргумента
parrot(actor='John Cleese') # неизвестный именованный аргумент
В вызове функции именованные аргументы должны следовать за позиционными. Все переданные именованные аргументы должны соответствовать одному из аргументов, принимаемых функцией (например, 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. Специальные параметры¶Special parameters
По умолчанию аргументы функции Python могут передаваться либо по позиции, либо явно по ключевому слову. Для удобочитаемости и производительности имеет смысл ограничить способ передачи аргументов, чтобы разработчику достаточно было взглянуть на определение функции и определить, передаются ли элементы по позиции, по позиции или по ключевому слову, или только по ключевому слову.
Определение функции может выглядеть так:
def f(pos1, pos2, /, pos_or_kwd, *, kwd1, kwd2):
----------- ---------- ----------
| | |
| Positional or keyword |
| - Keyword only
-- Positional only
где / и * являются необязательными. Если они используются, эти символы указывают на вид параметра в зависимости от того, как аргументы могут передаваться функции: только по позиции, по позиции или ключевому слову и только по ключевому слову. Параметры, передаваемые по ключевому слову, также называют именованными параметрами.
4.9.3.1. Аргументы, передаваемые по позиции или по ключевому слову¶Positional-or-Keyword Arguments
Если / и * отсутствуют в определении функции, аргументы могут передаваться функции по позиции или по ключевому слову.
4.9.3.2. Параметры только по позиции¶Positional-Only Parameters
Рассмотрим это подробнее: можно пометить некоторые параметры как только по позиции. Если параметры только по позиции, их порядок имеет значение, и их нельзя передавать по ключевому слову. Параметры только по позиции помещаются перед / (прямая косая черта). Символ / используется для логического отделения параметров только по позиции от остальных. Если в определении функции нет /, то параметров только по позиции нет.
Параметры, следующие после /, могут быть позиционными или ключевыми или только по ключевому слову.
4.9.3.3. Аргументы только по ключевому слову¶Keyword-Only Arguments
Чтобы пометить параметры как только по ключевому слову (то есть они должны передаваться только как именованные аргументы), поместите * в списке аргументов непосредственно перед первым только по ключевому слову параметром.
4.9.3.4. Примеры функций¶Function Examples
Рассмотрим следующие примеры определений функций, обращая особое внимание на маркеры / и *:
>>> 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. Итог¶Recap
Сценарий использования определяет, какие параметры использовать в определении функции:
def f(pos1, pos2, /, pos_or_kwd, *, kwd1, kwd2):
В качестве рекомендации:
Используйте параметры только по позиции, если вы не хотите, чтобы имена параметров были доступны пользователю. Это полезно, когда имена параметров не имеют реального значения, если нужно принудительно задать порядок аргументов при вызове функции или если требуется принимать некоторые позиционные параметры вместе с произвольными ключевыми словами.
Используйте параметры только по ключевому слову, когда имена имеют значение, и определение функции становится более понятным благодаря явному указанию имён, или если вы хотите предотвратить зависимость пользователей от порядка передаваемых аргументов.
Для API используйте параметры только по позиции, чтобы избежать разрушительных изменений API, если в будущем имя параметра будет изменено.
4.9.4. Произвольные списки аргументов¶Arbitrary Argument Lists
Наконец, наименее часто используемая возможность – указать, что функцию можно вызывать с произвольным количеством аргументов. Эти аргументы будут упакованы в кортеж (см. Кортежи и последовательности). Перед переменным числом аргументов может быть ноль или более обычных аргументов.
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. Распаковка списков аргументов¶Unpacking Argument Lists
Обратная ситуация возникает, когда аргументы уже находятся в списке или кортеже, но их нужно распаковать для вызова функции, требующей отдельных позиционных аргументов. Например, встроенная функция range() ожидает отдельные аргументы start и stop. Если они недоступны по отдельности, запишите вызов функции с оператором *, чтобы распаковать аргументы из списка или кортежа:
>>> list(range(3, 6)) # обычный вызов с отдельными аргументами
[3, 4, 5]
>>> args = [3, 6]
>>> list(range(*args)) # вызов с аргументами, распакованными из списка
[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 Expressions
Небольшие анонимные функции можно создать с помощью ключевого слова 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
В приведённом выше примере лямбда-выражение используется для возврата функции. Другое применение – передача небольшой функции в качестве аргумента:
>>> 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. Строки документации¶Documentation Strings
Вот некоторые соглашения о содержании и форматировании строк документации.
Первая строка всегда должна быть кратким и сжатым описанием назначения объекта. Для краткости в ней не следует явно указывать имя или тип объекта, так как они доступны другими способами (кроме случая, когда имя оказывается глаголом, описывающим действие функции). Эта строка должна начинаться с заглавной буквы и заканчиваться точкой.
Если в строке документации есть несколько строк, вторая строка должна быть пустой, чтобы визуально отделить краткое описание от остальной части. Последующие строки должны быть одним или несколькими абзацами, описывающими соглашения о вызове объекта, его побочные эффекты и т.д.
Парсер Python не удаляет отступы из многострочных строковых литералов в Python, поэтому инструменты, обрабатывающие документацию, должны при необходимости удалять отступы. Это делается по следующему соглашению. Первая непустая строка после первой строки строки определяет величину отступа для всей строки документации. (Первую строку мы не можем использовать, так как она обычно примыкает к открывающим кавычкам строки, и её отступ не очевиден в строковом литерале.) Затем пробельные символы, «эквивалентные» этому отступу, удаляются из начала всех строк строки. Строки с меньшим отступом встречаться не должны, но если они встречаются, все их начальные пробелы должны быть удалены. Эквивалентность пробелов следует проверять после замены табуляции (обычно на 8 пробелов).
Вот пример многострочной строки документации:
>>> def my_function():
... """Ничего не делает, но документирует.
...
... Нет, правда, он ничего не делает.
... """
... pass
...
>>> print(my_function.__doc__)
Do nothing, but document it.
No, really, it doesn't do anything.
4.9.8. Аннотации функций¶Function Annotations
Аннотации функций – это полностью необязательная метаинформация о типах, используемых пользовательскими функциями (см. 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. Интермеццо: Стиль кодирования¶Intermezzo: Coding Style
Теперь, когда вы собираетесь писать более длинные и сложные программы на Python, самое время поговорить о стиле кодирования. Большинство языков можно писать (или, точнее, форматировать) в разных стилях; одни стили более читаемы, чем другие. Облегчить чтение вашего кода другим – всегда хорошая идея, и принятие хорошего стиля кодирования в этом очень помогает.
Для Python PEP 8 стал руководством по стилю, которому следуют большинство проектов; он продвигает очень читаемый и приятный глазу стиль кодирования. Каждый разработчик Python должен прочитать его в какой-то момент; вот наиболее важные моменты, извлечённые для вас:
Используйте отступы в 4 пробела и никаких табуляций.
4 пробела – хороший компромисс между маленьким отступом (позволяет большую глубину вложенности) и большим отступом (легче читать). Табуляция вносит путаницу, и её лучше избегать.
Переносите строки так, чтобы они не превышали 79 символов.
Это помогает пользователям с маленькими экранами и позволяет размещать несколько файлов с кодом рядом на больших экранах.
Используйте пустые строки для разделения функций и классов, а также крупных блоков кода внутри функций.
По возможности размещайте комментарии на отдельных строках.
Используйте строки документации.
Используйте пробелы вокруг операторов и после запятых, но не непосредственно внутри скобочных конструкций:
a = f(1, 2) + g(3, 4).Давайте классам и функциям согласованные имена; принято использовать
UpperCamelCaseдля классов иlowercase_with_underscoresдля функций и методов. Всегда используйтеselfв качестве имени первого аргумента метода (см. Первый взгляд на классы для получения дополнительной информации о классах и методах).Не используйте экзотические кодировки, если ваш код предназначен для использования в международной среде. Стандартная кодировка Python, UTF-8 или даже обычный ASCII в любом случае подходят лучше всего.
Аналогично, не используйте символы, не входящие в ASCII, в идентификаторах, если есть хоть малейшая вероятность, что код будут читать или сопровождать люди, говорящие на другом языке.
Сноски
Эта страница – перевод. Оригинал на английском – docs.python.org. Нашли неточность? Сообщите нам.