Spec-Zone.ru › Python 3.12

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

Помимо только что рассмотренного оператора 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(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!
...

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 не совпадает, ни одна из ветвей не выполняется.

Можно объединить несколько литералов в одном шаблоне с помощью | («или»):

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 выше) никогда не получают значения.

Шаблоны могут быть сколь угодно вложенными. Например, если у нас есть небольшой список объектов 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 вводит определение функции. За ним должны следовать имя функции и заключённый в скобки список формальных параметров. Операторы, составляющие тело функции, начинаются со следующей строки и должны иметь отступ.

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

При вызове функции создаётся новая таблица символов для её локальных переменных. Точнее, все присваивания переменных в функции сохраняют значения в локальной таблице символов; при этом при поиске значения сначала проверяется локальная таблица символов, затем локальные таблицы символов охватывающих функций, после этого глобальная таблица символов и, наконец, таблица встроенных имён. Поэтому глобальным переменным и переменным охватывающих функций нельзя напрямую присвоить значение внутри функции (если только глобальная переменная не указана в операторе 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

В приведённом выше примере лямбда-выражение используется для возврата функции. Ещё один вариант применения — передача небольшой функции в качестве аргумента:

>>> 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–2024 Python Software Foundation
Licensed under the PSF License.
https://docs.python.org/3.12/tutorial/controlflow.html

Spec-Zone.ru

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