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), оператор Python for итерирует по элементам любого последовательности (список или строка) в порядке их появления в последовательности. Например (без намека на игру слов):

>>> # 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: блок try оператора else выполняется, если не происходит исключение, а блок 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!
...
END_OF_DOCUMENT_MARKER

4.7. match Операторы

Оператор match принимает выражение и сравнивает его значение с последовательными шаблонами, заданными одним или несколькими блоками case. Это внешне похоже на оператор switch в C, Java или JavaScript (и во многих других языках), но он больше похож на сопоставление с образцом в языках, таких как Rust или Haskell. Выполняется только первый совпавший шаблон, и он также может извлекать компоненты (элементы последовательности или атрибуты объекта) из значения в переменные.

Простейшая форма сравнивает значение объекта с одним или несколькими литералами:

def http_error(status):
    match status:
        case 400:
            return "Bad request"
        case 404:
            return "Not found"
        case 418:
            return "I'm a teapot"
        case _:
            return "Something's wrong with the internet"

Обратите внимание на последний блок: «имя переменной» _ действует как подстановочный символ и всегда соответствует. Если ни один шаблон не совпадает, ни один из ответвлений не выполняется.

Вы можете объединить несколько литералов в одном шаблоне, используя | («или»):

case 401 | 403 | 404:
    return "Not allowed"

Шаблоны могут выглядеть как распаковки присваиваний и могут использоваться для привязки переменных:

# point is an (x, y) tuple
match point:
    case (0, 0):
        print("Origin")
    case (0, y):
        print(f"Y={y}")
    case (x, 0):
        print(f"X={x}")
    case (x, y):
        print(f"X={x}, Y={y}")
    case _:
        raise ValueError("Not a point")

Внимательно изучите этот пример! Первый шаблон имеет два литерала и можно рассматривать как расширение показанного выше шаблона литерала. Но следующие два шаблона объединяют литерал и переменную, и переменная привязывает значение из объекта (point). Четвертый шаблон захватывает два значения, что концептуально аналогично распаковке присваивания (x, y) = point.

Если для структурирования данных вы используете классы, вы можете использовать имя класса, за которым следует список аргументов, напоминающий конструктор, но с возможностью захвата атрибутов в переменные:

class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y

def where_is(point):
    match point:
        case Point(x=0, y=0):
            print("Origin")
        case Point(x=0, y=y):
            print(f"Y={y}")
        case Point(x=x, y=0):
            print(f"X={x}")
        case Point():
            print("Somewhere else")
        case _:
            print("Not a point")

Вы можете использовать позиционные параметры с некоторыми встроенными классами, которые обеспечивают порядок для их атрибутов (например, dataclasses). Вы также можете определить определенную позицию для атрибутов в шаблонах, задав __match_args__ специальный атрибут в ваших классах. Если он установлен в («x», «y»), следующие шаблоны являются эквивалентными (и все привязывают атрибут y к переменной var):

Point(1, var)
Point(1, y=var)
Point(x=1, y=var)
Point(y=var, x=1)

Рекомендуемый способ чтения шаблонов — рассматривать их как расширенную форму того, что вы бы поставили слева от присваивания, чтобы понять, какие переменные будут установлены на какие значения. Только автономные имена (например, var выше) присваиваются оператором сопоставления. Точечные имена (например, foo.bar), имена атрибутов (x= и y= выше) или имена классов (распознаваемые по «(…)» рядом с ними, например Point выше) никогда не присваиваются.

Шаблоны могут быть произвольно вложены. Например, если у нас есть короткий список точек с __match_args__ добавленным, мы можем сопоставить его следующим образом:

class Point:
    __match_args__ = ('x', 'y')
    def __init__(self, x, y):
        self.x = x
        self.y = y

match points:
    case []:
        print("No points")
    case [Point(0, 0)]:
        print("The origin")
    case [Point(x, y)]:
        print(f"Single point {x}, {y}")
    case [Point(0, y1), Point(0, y2)]:
        print(f"Two on the Y axis at {y1}, {y2}")
    case _:
        print("Something else")

Мы можем добавить условие if к шаблону, известному как «фильтр». Если фильтр ложный, match переходит к следующему блоку case. Обратите внимание, что захват значения происходит до оценки фильтра:

match point:
    case Point(x, y) if x == y:
        print(f"Y=X at {x}")
    case Point(x, y):
        print(f"Not on the diagonal")

Несколько других ключевых функций этого оператора:

  • Подобно распаковке присваиваний, кортежи и списки шаблонов имеют точно такое же значение и фактически соответствуют произвольным последовательностям. Важное исключение состоит в том, что они не соответствуют итераторам или строкам.
  • Шаблоны последовательностей поддерживают расширенную распаковку: [x, y, *rest] и (x, y, *rest) работают аналогично распаковке присваиваний. Имя после * также может быть _, поэтому (x, y, *_) соответствует последовательности, содержащей как минимум два элемента, без привязки оставшихся элементов.
  • Шаблоны словарей: {"bandwidth": b, "latency": l} захватывает значения "bandwidth" и "latency" из словаря. В отличие от шаблонов последовательностей, дополнительные ключи игнорируются. Также поддерживается распаковка, как в **rest. (Но **_ было бы избыточным, поэтому оно запрещено.)
  • Подшаблоны можно захватить с помощью ключевого слова as:

    case (Point(x1, y1), Point(x2, y2) as p2): ...
    

    будет захватывать второй элемент ввода как p2 (при условии, что ввод является последовательностью из двух точек)

  • Большинство литералов сравниваются по равенству, однако одиночные объекты True, False и None сравниваются по идентичности.
  • Шаблоны могут использовать именованные константы. Они должны быть точечными именами, чтобы предотвратить их интерпретацию как переменные захвата:

    from enum import Enum
    class Color(Enum):
        RED = 'red'
        GREEN = 'green'
        BLUE = 'blue'
    
    color = Color(input("Enter your choice of 'red', 'blue' or 'green': "))
    
    match color:
        case Color.RED:
            print("I see red!")
        case Color.GREEN:
            print("Grass is green")
        case Color.BLUE:
            print("I'm feeling the blues :(")
    

Для более подробного объяснения и дополнительных примеров можно обратиться к PEP 636, написанному в формате руководства.

4.8. Определение функций

Мы можем создать функцию, которая записывает ряд Фибоначчи в произвольную границу:

>>> def fib(n):    # write Fibonacci series less than n
...     """Print a Fibonacci series less than n."""
...     a, b = 0, 1
...     while a < n:
...         print(a, end=' ')
...         a, b = b, a+b
...     print()
...
>>> # Now call the function we just defined:
>>> fib(2000)
0 1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 1597

Ключевое слово def вводит определение функции. За ним должно следовать имя функции и список формальных параметров в скобках. Операторы, составляющие тело функции, начинаются с новой строки и должны быть отступом.

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

Выполнение функции создает новую таблицу символов, используемую для локальных переменных функции. Точнее, все присваивания переменных в функции сохраняют значение в локальной таблице символов; при этом ссылки на переменные сначала ищутся в локальной таблице символов, затем в локальных таблицах символов окружающих функций, затем в глобальной таблице символов и, наконец, в таблице встроенных имен. Таким образом, глобальные переменные и переменные окружающих функций не могут быть непосредственно присвоены значение в функции (если, для глобальных переменных, они не указаны в операторе 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. Эта функция возвращает сумму её двух аргументов: lambda a, b: a+b. Lambda-функции могут быть использованы везде, где требуются объекты функций. Они синтаксически ограничены одним выражением. Семантически, они представляют собой просто синтаксический сахар для обычного определения функции. Как и вложенные определения функций, lambda-функции могут ссылаться на переменные из содержащей области видимости:

>>> def make_incrementor(n):
...     return lambda x: x + n
...
>>> f = make_incrementor(42)
>>> f(0)
42
>>> f(1)
43

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

>>> 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.13/tutorial/controlflow.html

Spec-Zone.ru

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