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

Помимо только что представленного оператора 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» и полезно для того, чтобы избежать чрезмерного отступа. Последовательность ifelifelif … заменяет операторы 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(), ведет себя так, как будто это список, но на самом деле это не так. Это объект, который возвращает последовательные элементы нужной последовательности при повторении по нему, но на самом деле он не входит в список, что позволяет экономить место.

Мы говорим, что такой объект является iterable, то есть подходит в качестве цели для функций и конструкций, которые ожидают чего-то, из чего они могут получать последовательные элементы до тех пор, пока предложение не будет исчерпано. Мы видели, что оператор 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 предложение оператора else выполняется, когда не возникает исключения, а предложение цикла else выполняется, когда не возникает break. Подробнее об инструкции try и исключениях читайте в разделе Обработка исключений.

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

>>> 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!
...

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

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

При выполнении функции вводится новая таблица символов, используемая для локальных переменных функции. Точнее, все присвоения переменных в функции сохраняют значение в локальной таблице символов; в то время как ссылки на переменные сначала просматриваются в локальной таблице символов, затем в локальных таблицах символов входящих функций, затем в глобальной таблице символов и, наконец, в таблице встроенных имен. Таким образом, глобальным переменным и переменным входящих функций нельзя напрямую присвоить значение внутри функции (за исключением случаев, когда для глобальных переменных, указанных в инструкции 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) вызывает метод объекта list 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:
        ok = input(prompt)
        if ok in ('y', 'ye', 'yes'):
            return True
        if ok 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. Аргументы ключевых слов

Функции также могут быть вызваны с помощью keyword arguments вида kwarg=value. Например, следующая функция:

def parrot(voltage, state='a stiff', action='voom', type='Norwegian Blue'):
    print("-- This parrot wouldn't", action, end=' ')
    print("if you put", voltage, "volts through it.")
    print("-- Lovely plumage, the", type)
    print("-- It's", state, "!")

принимает один обязательный аргумент (voltage) и три необязательных аргумента (state, action, и type). Эта функция может быть вызвана любым из следующих способов:

parrot(1000)                                          # 1 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 (описанным в следующем подразделе), который получает tuple, содержащий позиционные аргументы, не входящие в список формальных параметров. (*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 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.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. Функциональные аннотации

Function annotations - это совершенно необязательные метаданные о типах, используемых пользовательскими функциями (смотрите PEP 3107 и PEP 484 для получения дополнительной информации).

Annotations хранятся в атрибуте __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, в идентификаторах, если есть хоть малейший шанс, что люди, говорящие на другом языке, прочтут или сохранят код.

Сноски

Вернуться на верх