Spec-Zone.ru › Python 3.10

Более сложные инструменты управления потоком

Помимо оператора 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(10)
range(0, 10)

Во многих отношениях объект, возвращаемый функцией range(), ведёт себя так, как будто это список, но на самом деле это не так. Это объект, который возвращает последовательные элементы нужной последовательности при итерировании по нему, но фактически не создаёт сам список, что экономит память.

Мы говорим, что такой объект является итерируемым, то есть подходящим в качестве целевого объекта для функций и конструкций, ожидающих последовательный доступ к элементам до исчерпания источника. Мы видели, что оператор for является такой конструкцией, а примером функции, которая принимает итерируемый объект, является sum():

>>> sum(range(4))  # 0 + 1 + 2 + 3
6

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

4.4. break и continue операторы, и else-блоки в циклах

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

Операторы циклов могут иметь else-блоки; они выполняются, когда цикл завершается исчерпанием итерируемого объекта (с for) или когда условие становится ложным (с while), но не когда цикл завершается оператором break. Это иллюстрируется следующим циклом, который ищет простые числа:

>>> 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: блок else оператора 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 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:
    x: int
    y: int

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 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) вызывает метод объекта списка 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. Аргументы по ключевым словам

Функции также можно вызывать, используя аргументы по ключевым словам вида 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–2023 Python Software Foundation
Licensed under the PSF License.
https://docs.python.org/3.10/tutorial/controlflow.html

Spec-Zone.ru

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