38. Мини код ревью Python кода (Продолжение)
Условие задачи: Расскажи как работает код ниже
import datetime
from functools import wraps
import time
def decorator(func):
@wraps(func)
def inner(*args, **kwargs):
start_time = datetime.datetime.now()
result = func(*args, **kwargs)
time.sleep(2)
end_time = datetime.datetime.now()
print(end_time - start_time)
return result
return inner
@decorator('test')
def start_func(a, b):
return a + b
t = start_func(1, 2)
print(t)
# В чем проблема этого кода?
from fastapi import FastAPI
app = FastAPI()
@app.get("/item")
async def read_users():
return ["Rick", "Morty"]
@app.get("/item")
async def read_users2():
return ["Bean", "Elfo"]
def func1():
print(1)
def func2():
print(2)
res1 = func1() and func2()
res2 = 0 or '5'
print(res1)
print(res2)
row = [['_'] * 3] * 3
row[1][2] = 'X'
print(row)
# Выбери вариант ответа
1 — [['_', '_', '_'], ['_', '_', 'X'], ['_', '_', '_']]
2 — [['_', '_', 'X'], ['_', '_', 'X'], ['_', '_', 'X']]
Спойлеры к решению
Подсказки
- В первом фрагменте проблема в строке
@decorator('test'). - Функция
decoratorнаписана как обычный декоратор, а используется как декоратор с аргументом. - Во FastAPI-фрагменте два обработчика зарегистрированы на один и тот же путь
GET /item. - В выражении
func1() and func2()вторая функция не вызовется, потому чтоfunc1()возвращаетNone. - В выражении
0 or '5'результатом будет строка'5'. - В
row = [['_'] * 3] * 3создаются три ссылки на один и тот же внутренний список.
Решение
Первый фрагмент:
import datetime
from functools import wraps
import time
def decorator(func):
@wraps(func)
def inner(*args, **kwargs):
start_time = datetime.datetime.now()
result = func(*args, **kwargs)
time.sleep(2)
end_time = datetime.datetime.now()
print(end_time - start_time)
return result
return inner
@decorator('test')
def start_func(a, b):
return a + b
t = start_func(1, 2)
print(t)
Код упадёт с ошибкой ещё на этапе объявления функции:
TypeError: 'str' object is not callable
Проблема здесь:
@decorator('test')
Функция decorator ожидает функцию:
def decorator(func):
Но ей передали строку 'test'. Поэтому внутри decorator переменная func становится строкой, а не функцией.
Дальше Python пытается применить результат decorator('test') к функции start_func. В итоге вызывается inner(start_func), а внутри inner выполняется:
result = func(*args, **kwargs)
Но func — это строка 'test', а строку нельзя вызвать как функцию. Поэтому возникает ошибка.
Правильный вариант без аргумента у декоратора:
import datetime
from functools import wraps
import time
def decorator(func):
@wraps(func)
def inner(*args, **kwargs):
start_time = datetime.datetime.now()
result = func(*args, **kwargs)
time.sleep(2)
end_time = datetime.datetime.now()
print(end_time - start_time)
return result
return inner
@decorator
def start_func(a, b):
return a + b
t = start_func(1, 2)
print(t)
Примерный результат:
0:00:02.000...
3
Если нужно именно использовать запись @decorator('test'), тогда нужен декоратор-фабрика:
import datetime
from functools import wraps
import time
def decorator(name):
def outer(func):
@wraps(func)
def inner(*args, **kwargs):
start_time = datetime.datetime.now()
result = func(*args, **kwargs)
time.sleep(2)
end_time = datetime.datetime.now()
print(name)
print(end_time - start_time)
return result
return inner
return outer
@decorator("test")
def start_func(a, b):
return a + b
t = start_func(1, 2)
print(t)
Второй фрагмент:
from fastapi import FastAPI
app = FastAPI()
@app.get("/item")
async def read_users():
return ["Rick", "Morty"]
@app.get("/item")
async def read_users2():
return ["Bean", "Elfo"]
Проблема в том, что оба обработчика зарегистрированы на один и тот же маршрут:
GET /item
GET /item
Это дублирование маршрута. Для одного HTTP-метода и одного пути должен быть один обработчик.
На практике первый зарегистрированный обработчик будет найден раньше:
@app.get("/item")
async def read_users():
return ["Rick", "Morty"]
А второй обработчик с тем же путём фактически станет недостижимым для обычного запроса GET /item.
Правильно сделать разные пути:
from fastapi import FastAPI
app = FastAPI()
@app.get("/rick-and-morty")
async def read_users():
return ["Rick", "Morty"]
@app.get("/disenchantment")
async def read_users2():
return ["Bean", "Elfo"]
Третий фрагмент:
def func1():
print(1)
def func2():
print(2)
res1 = func1() and func2()
res2 = 0 or '5'
print(res1)
print(res2)
Результат:
1
None
5
Разбор:
res1 = func1() and func2()
Сначала вызывается func1(). Она печатает:
1
Но явно ничего не возвращает, значит результат функции — None.
Получается:
res1 = None and func2()
None — ложное значение, поэтому оператор and останавливается на первом ложном значении и func2() уже не вызывается.
Поэтому:
res1 = None
Дальше:
res2 = 0 or '5'
0 — ложное значение, поэтому or возвращает первое истинное значение справа, то есть строку '5'.
Четвёртый фрагмент:
row = [['_'] * 3] * 3
row[1][2] = 'X'
print(row)
Правильный вариант ответа:
2 — [['_', '_', 'X'], ['_', '_', 'X'], ['_', '_', 'X']]
Результат:
[['_', '_', 'X'], ['_', '_', 'X'], ['_', '_', 'X']]
Причина в строке:
row = [['_'] * 3] * 3
Она создаёт не три независимых списка, а три ссылки на один и тот же внутренний список.
То есть все строки матрицы ссылаются на один объект. Поэтому изменение:
row[1][2] = 'X'
меняет третий элемент в общем внутреннем списке, и это видно сразу во всех трёх строках.
Правильное создание независимых строк:
row = [['_'] * 3 for _ in range(3)]
row[1][2] = 'X'
print(row)
Результат будет:
[['_', '_', '_'], ['_', '_', 'X'], ['_', '_', '_']]