Spec-Zone.ru › Python 3.13

Дополнительные средства управления потоком

Помимо только что рассмотренного оператора 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 выполняется после завершения последней итерации цикла, то есть если не произошло прерывание с помощью break.

В цикле 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")

Некоторые встроенные классы, задающие порядок своих атрибутов (например, классы данных), позволяют использовать позиционные параметры. Также можно задать определённые позиции атрибутов в шаблонах, установив специальный атрибут __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, называемую «защитой». Если защита имеет значение false, 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 вводит определение функции. За ним должны следовать имя функции и заключённый в скобки список формальных параметров. Операторы, составляющие тело функции, начинаются со следующей строки и должны иметь отступ.

Первым оператором тела функции может быть строковый литерал; такой строковый литерал является строкой документации функции, или докстрингом. (Подробнее о докстрингах см. в разделе Строки документации.) Существуют инструменты, которые используют докстринги для автоматического создания онлайн-документации или документации для печати, а также позволяют пользователю интерактивно просматривать код. Рекомендуется включать докстринги в написанный вами код — возьмите это за привычку.

Выполнение функции создаёт новую таблицу символов, используемую для локальных переменных функции. Точнее, все присваивания переменных в функции сохраняют значения в локальной таблице символов; при этом ссылки на переменные сначала ищутся в локальной таблице символов, затем в локальных таблицах символов охватывающих функций, далее в глобальной таблице символов и, наконец, в таблице встроенных имён. Поэтому глобальным переменным и переменным охватывающих функций нельзя напрямую присваивать значения внутри функции (если только глобальные переменные не указаны в операторе 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 не удаляет отступы в многострочных строковых литералах Python, поэтому инструменты обработки документации должны удалять отступы, если это необходимо. Для этого используется следующее соглашение. Первая непустая строка после первой строки строки определяет величину отступа для всей строки документации. (Первую строку использовать нельзя, поскольку она обычно примыкает к открывающим кавычкам строки, поэтому её отступ не виден в строковом литерале.) Затем из начала всех строк удаляется объём пробельных символов, «эквивалентный» этому отступу. Строк с меньшим отступом быть не должно, но если они есть, из них удаляются все начальные пробельные символы. Эквивалентность пробельных символов следует проверять после преобразования табуляций (обычно в 8 пробелов).

Вот пример многострочной строки документации:

>>> def my_function():
...     """Do nothing, but document it.
...
...     No, really, it doesn't do anything.
...     """
...     pass
...
>>> print(my_function.__doc__)
Do nothing, but document it.

No, really, it doesn't do anything.

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 пробела, а не табуляцию.

    4 пробела — хороший компромисс между небольшим отступом (позволяет увеличить глубину вложенности) и большим отступом (облегчает чтение). Табуляция может вызвать путаницу, поэтому её лучше не использовать.

  • Ограничивайте длину строк 79 символами.

    Это удобно для пользователей с небольшими экранами и позволяет размещать рядом несколько файлов с кодом на больших экранах.

  • Отделяйте функции и классы, а также крупные блоки кода внутри функций пустыми строками.
  • По возможности размещайте комментарии на отдельных строках.
  • Используйте строки документации.
  • Ставьте пробелы вокруг операторов и после запятых, но не непосредственно внутри конструкций с ограничителями: a = f(1, 2) + g(3, 4).
  • Придерживайтесь единообразного стиля именования классов и функций: принято использовать UpperCamelCase для классов и lowercase_with_underscores для функций и методов. Всегда используйте self в качестве имени первого аргумента метода (подробнее о классах и методах см. в разделе Первое знакомство с классами).
  • Не используйте необычные кодировки, если ваш код предназначен для международной аудитории. В любом случае лучше всего подходят кодировка Python по умолчанию UTF-8 или даже обычный ASCII.
  • Также не используйте символы, отличные от ASCII, в идентификаторах, если есть хоть малейшая вероятность, что код будут читать или поддерживать люди, говорящие на другом языке.

Сноски

[1]

На самом деле точнее было бы сказать передача по ссылке на объект, поскольку при передаче изменяемого объекта вызывающая сторона увидит все изменения, внесённые в него вызываемой стороной (например, добавленные в список элементы).

© 2001–2025 Python Software Foundation
Licensed under the PSF License.
https://docs.python.org/3.13/tutorial/controlflow.html

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API