Как только проверок становится больше одной, появляется повторение: тот же расчёт, те же три строки подготовки данных. Скопировать их в десять мест — значит потом менять в десяти местах. Функция решает это: код пишется один раз и вызывается по имени.
Параметры входят, результат выходит через return. Меняется тело функции — меняется поведение везде, откуда её зовут.
Как выглядит функция
живой пример
def поздороваться():
print("Привет")
поздороваться() # вызов: теперь напечатается
Запустить
Запуск примеров доступен в платном доступе. Там этот же код выполняется прямо в статье: редактор, запуск и проверка рядом с абзацем. Три дня бесплатно →
def — «определяю функцию», дальше имя, скобки и двоеточие. Тело — со сдвигом.
Пока функцию не вызвали, внутри неё ничего не происходит.
Отработать здесь: Своя функция
Параметры: одно и то же, но с разными данными
живой пример
def поздороваться(имя):
print(f"Привет, {имя}")
поздороваться("Анна") # Привет, Анна
поздороваться("Иван") # Привет, Иван
Запустить
Запуск примеров доступен в платном доступе. Там этот же код выполняется прямо в статье: редактор, запуск и проверка рядом с абзацем. Три дня бесплатно →
Имя в скобках — параметр: переменная, которая появится внутри функции с тем значением, которое передали при вызове. Параметров может быть несколько, через запятую.
Отработать здесь: Функция с параметром · Два параметра и расчёт
Возврат значения
Печатать изнутри функции почти всегда неправильно: результат нужен не на экране, а в коде — чтобы сравнить или передать дальше.
живой пример
# так не надо
def стоимость(цена, количество):
print(цена * количество)
стоимость(4990, 2)
Запустить
Запуск примеров доступен в платном доступе. Там этот же код выполняется прямо в статье: редактор, запуск и проверка рядом с абзацем. Три дня бесплатно →
живой пример
# так надо
def стоимость(цена, количество):
return цена * количество
итого = стоимость(4990, 2)
print(итого > 5000)
Запустить
Запуск примеров доступен в платном доступе. Там этот же код выполняется прямо в статье: редактор, запуск и проверка рядом с абзацем. Три дня бесплатно →
Первая умеет только показать число человеку. Вторая отдаёт его в код: return
заканчивает функцию и передаёт значение наружу, его кладут в переменную и
сравнивают. Всё после return не выполнится.
Отработать здесь: Функция, которая отвечает «да» или «нет»
Значения по умолчанию
живой пример
def найти_заказы(заказы, статус="PAID"):
return [з for з in заказы if з["status"] == статус]
заказы = [
{"id": "ord-042", "status": "PAID"},
{"id": "ord-043", "status": "CANCELLED"},
]
print(найти_заказы(заказы)) # ищет PAID
print(найти_заказы(заказы, "CANCELLED")) # ищет отменённые
Запустить
Запуск примеров доступен в платном доступе. Там этот же код выполняется прямо в статье: редактор, запуск и проверка рядом с абзацем. Три дня бесплатно →
Параметр со значением по умолчанию можно не передавать: у функции есть обычный случай, а исключения передают явно.
Одна ловушка: значением по умолчанию не должен быть список или словарь.
живой пример
# так не надо
def добавить(элемент, куда=[]):
куда.append(элемент)
return куда
print(добавить("a"))
print(добавить("b")) # ['a', 'b'] — след прошлого вызова
Запустить
Запуск примеров доступен в платном доступе. Там этот же код выполняется прямо в статье: редактор, запуск и проверка рядом с абзацем. Три дня бесплатно →
живой пример
# так надо
def добавить(элемент, куда=None):
if куда is None:
куда = []
куда.append(элемент)
return куда
print(добавить("a"))
print(добавить("b")) # ['b'] — каждый вызов со своим списком
Запустить
Запуск примеров доступен в платном доступе. Там этот же код выполняется прямо в статье: редактор, запуск и проверка рядом с абзацем. Три дня бесплатно →
Список в скобках создаётся один раз на всю программу, а не при каждом вызове, и
копит значения между ними. С None он создаётся заново внутри функции.
Отработать здесь: Значение по умолчанию
Зачем это тестировщику
Подготовка данных. Заказ, на котором идёт проверка, собирают функцией: общее внутри, различия — параметрами.
живой пример
def заказ(статус="PAID", сумма=4990):
return {"id": "ord-042", "status": статус, "total": сумма}
print(заказ())
print(заказ("CANCELLED", 890))
Запустить
Запуск примеров доступен в платном доступе. Там этот же код выполняется прямо в статье: редактор, запуск и проверка рядом с абзацем. Три дня бесплатно →
Дальше в каждой проверке одна строка вместо трёх, а при изменении состава заказа правится одно место.
Своя проверка. Условие, которое повторяется, выносят и называют по смыслу:
живой пример
def сумма_сходится(заказ):
посчитано = 0
for позиция in заказ["items"]:
посчитано += позиция["qty"] * позиция["price"]
return посчитано == заказ["total"]
заказ = {"items": [{"qty": 1, "price": 4990}, {"qty": 2, "price": 890}], "total": 6770}
print(сумма_сходится(заказ)) # True
Запустить
Запуск примеров доступен в платном доступе. Там этот же код выполняется прямо в статье: редактор, запуск и проверка рядом с абзацем. Три дня бесплатно →
Такая функция отвечает «да» или «нет»: читающий видит if not сумма_сходится(заказ) и понимает намерение без разбора арифметики.
Отработать здесь: Разложить проверку на две части
Как называть и как оформлять
Имя функции — глагол или вопрос: найти_заказы, добавить, сумма_сходится. В
рабочем проекте пишут по-английски: find_orders, add, total_adds_up. Если
имя получается длинным вроде сделать_всё_и_проверить — функция делает слишком
много, её стоит разделить.
У питона есть общее соглашение об оформлении — PEP 8. В работе из него держат в
голове четыре правила: отступ — четыре пробела (табы с пробелами не смешивают,
питон ругнётся); имена функций и переменных — строчными через подчёркивание;
вокруг = в присваивании пробелы, а у значения по умолчанию в скобках — нет;
строка не длиннее 79 знаков по PEP 8, а в большинстве команд договариваются примерно о ста.
живой пример
# так не надо: имя ни о чём, пробелы где попало
def Проверка( заказ,минимум = 1000 ):
return заказ [ "total" ]>=минимум
print(Проверка({"total": 4990}))
Запустить
Запуск примеров доступен в платном доступе. Там этот же код выполняется прямо в статье: редактор, запуск и проверка рядом с абзацем. Три дня бесплатно →
живой пример
# так надо: имя-вопрос, четыре пробела отступа, ровные пробелы
def сумма_не_меньше(заказ, минимум=1000):
return заказ["total"] >= минимум
print(сумма_не_меньше({"total": 4990}))
Запустить
Запуск примеров доступен в платном доступе. Там этот же код выполняется прямо в статье: редактор, запуск и проверка рядом с абзацем. Три дня бесплатно →
Оба варианта работают одинаково, но второй читается с первого взгляда — а код чаще читают, чем пишут. Следить за этим руками не нужно: редактор и форматировщик сами приводят файл к общему виду.
Глубже: класс и объект: зачем это в автотестахрасширенное
Функции хватает, пока данные и действия над ними живут отдельно. Как только появляется «корзина, в которую кладут товары и у которой спрашивают итог», удобнее держать данные и функции вместе. Для этого есть класс: описание того, из чего объект состоит и что умеет. Объект это конкретный экземпляр, созданный по классу.
живой пример
class Cart:
def __init__(self):
self.items = []
def add(self, name, price):
self.items.append({"name": name, "price": price})
def total(self):
return sum(item["price"] for item in self.items)
cart = Cart()
cart.add("чай", 300)
cart.add("кофе", 500)
print(len(cart.items), cart.total()) # 2 800
Запустить
Запуск примеров доступен в платном доступе. Там этот же код выполняется прямо в статье: редактор, запуск и проверка рядом с абзацем. Три дня бесплатно →
__init__ выполняется при создании объекта и задаёт начальные данные, self это сам объект, через который методы читают и меняют его поля. В автотестах классы встречаются постоянно: страница входа как класс с методами open, login и error_text, это Page Object, и тест читается как сценарий, а не как набор локаторов. Четыре слова про ООП, которые спрашивают: инкапсуляция, данные и методы вместе; наследование, класс берёт поведение родителя; полиморфизм, один метод по-разному у разных классов; абстракция, снаружи виден только нужный интерфейс. Разобраны они в статье про ООП в Python.
Коротко
def имя(параметры):объявляет функцию; пока её не вызвали, внутри ничего не происходит.returnотдаёт результат в код и заканчивает функцию; печатать изнутри — почти всегда ошибка.- Значение по умолчанию делает параметр необязательным; списком или словарём оно быть не должно.
- В проверках функции закрывают две задачи: подготовка данных и своя проверка с понятным именем.
- Имя — глагол или вопрос; слишком длинное имя означает, что функция делает слишком много.
- Класс описывает данные и методы вместе, объект это экземпляр,
__init__задаёт начальные данные,selfэто сам объект; так устроен Page Object.
Что почитать дальше
- Списки и словари — из чего состоят данные, которые функция принимает и возвращает.
- assert и ошибки — как проверка падает понятно.
- Разбор данных — считать, сверить, найти расхождение.
- pytest: первый настоящий тест — как эти функции превращаются в тест, который запускают одной командой.