Мини код ревью Python кода (Продолжение)

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'], ['_', '_', '_']]