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 операторы и else блоки в циклах

Оператор break прерывает выполнение ближайшего вложенного цикла for или while.

Цикл for или while может содержать else блок.

В цикле for блок else выполняется после завершения последней итерации.

В цикле while он выполняется после того, как условие цикла станет ложным.

В обоих типах циклов блок else не выполняется, если цикл был прерван оператором break.

Это проиллюстрировано в следующем цикле 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 имеет больше общего с блоком else оператора try, чем с блоком if: блок try оператора try выполняется, когда не происходит исключения, а блок else цикла выполняется, когда не возникает break. Дополнительные сведения об операторе try и исключениях см. в разделе Обработка исключений.

Оператор continue, также заимствованный из C, продолжает выполнение с следующей итерацией цикла:

>>> for num in range(2, 10):
...     if num % 2 == 0:
...         print("Found an even number", num)
...         continue
...     print("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. 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.6. 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)

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

>>> def fib(n):    # write Fibonacci series up to n
...     """Print a Fibonacci series up to 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.8. Более подробная информация о определении функций

Также можно определять функции с переменным числом аргументов. Существует три формы, которые можно комбинировать.

4.8.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.8.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.8.3. Специальные параметры

По умолчанию аргументы могут передаваться в Python-функцию либо по положению, либо явно по ключевому слову. Для повышения читаемости и производительности имеет смысл ограничить способ передачи аргументов, чтобы разработчик мог определить способ передачи элементов по положению, по положению или ключевому слову, или по ключевому слову, просто взглянув на определение функции.

Определение функции может выглядеть так:

def f(pos1, pos2, /, pos_or_kwd, *, kwd1, kwd2):
      -----------    ----------     ----------
        |             |                  |
        |        Positional or keyword   |
        |                                - Keyword only
         -- Positional only

где / и * являются необязательными. Если они используются, эти символы указывают тип параметра в зависимости от того, как аргументы могут передаваться в функцию: только по положению, по положению или по ключевому слову, и только по ключевому слову. Аргументы по ключевым словам также называют именованными аргументами.

4.8.3.1. Аргументы по положению или по ключевым словам

Если / и * отсутствуют в определении функции, аргументы могут передаваться в функцию по положению или по ключевому слову.

4.8.3.2. Только позиционные параметры

Рассмотрим это подробнее. Можно пометить определенные параметры как только позиционные. Если только позиционные, порядок параметров имеет значение, и параметры нельзя передать по ключевому слову. Только позиционные параметры помещаются перед / (слеш). / используется для логического разделения только позиционных параметров от остальных параметров. Если в определении функции нет /, только позиционных параметров нет.

Параметры, следующие за /, могут быть позиционными или ключевыми или только ключевыми.

4.8.3.3. Только аргументы по ключевым словам

Чтобы пометить параметры как только ключевые, указывая, что параметры должны передаваться как аргументы по ключевым словам, поместите * в список аргументов непосредственно перед первым только ключевым параметром.

4.8.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_args допускаются только аргументы по ключевым словам, как указано * в определении функции:

>>> 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.8.3.5. Резюме

Случай использования определит, какие параметры использовать в определении функции:

def f(pos1, pos2, /, pos_or_kwd, *, kwd1, kwd2):

В качестве руководства:

  • Используйте только позиционные, если хотите, чтобы имя параметров не было доступно пользователю. Это полезно, когда имена параметров не имеют реального смысла, если вы хотите принудительно установить порядок аргументов при вызове функции или если вам нужно взять некоторые позиционные параметры и произвольные ключевые слова.
  • Используйте только ключевые, когда имена имеют смысл и определение функции более понятно благодаря явности с именами, или если вы хотите предотвратить использование пользователем положения передаваемого аргумента.
  • Для API используйте только позиционные, чтобы предотвратить внесение изменений, нарушающих API, если имя параметра будет изменено в будущем.

4.8.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.8.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.8.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.8.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.8.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.9. Интермеццо: Стиль кодирования

Теперь, когда вы собираетесь писать более длинные и сложные фрагменты 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