Lord_of_Rings
@Lord_of_Rings
Python developer

Почему приложение Flask не работает в классе?

Вот такой код
from flask import Flask, render_template 

class Web:
    app = Flask(__name__) 

    @app.route('/') 
    def index(self):
        return render_template('index.html')

Web().app.run()


Выдаёт ошибку, что типа Web.index() принимает аргумент self
  • Вопрос задан
  • 212 просмотров
Решения вопроса 1
Vindicar
@Vindicar
RTFM!
Чтобы понять, нужно понимать три вещи про питон.
Во-первых, в нём всё - оператор. Да-да.
def - это оператор объявления функции.
class - оператор объявления класса.
Тебе никто не запрещает сделать
if condition:
    def foo():
        pass
else:
    def bar():
        pass


Во-вторых, все методы класса существуют на уровне класса. Явная передача self на это намекает.
Иными словами:
class Foo:
    def bar(self, baz):
        print(self, baz)

foo = Foo()
# вот этот вызов
foo.bar('hallo!')
# полностью эквивалентен вот этому
Foo.bar(foo, 'hallo!')

А когда ты обращаешься к foo.bar, Питон конструирует специальный временный объект-обёртку, который ссылается на Foo.bar(), но в то же время подставляет туда первым параметром тот объект foo, к которому произошло обращение. Так что если Foo.bar() принимает два параметра (self и baz), то foo.bar() принимает уже один (baz), так как правильный self будет подставлен этой обёрткой самостоятельно.

В-третьих, декоратор - это просто синтаксический сахар для вызова функции. Иными словами:
@app.route('/') 
def index(self):
    return render_template('index.html')

будет эквивалентно вот такому коду:
def index(self):
    return render_template('index.html')

_decorator = app.route('/')
index = _decorator(index)

Причем это будет работать одинаково и вне класса, и внутри класса.
Как это относится к твоему вопросу? А вот как.
class Web:
    app = Flask(__name__) 

    @app.route('/') 
    def index(self):
        return render_template('index.html')

Последовательность действий тут такова:
1. Создаётся пространство имён для нового класса, пока что безымянного.
2. В этом пространстве имён создаётся переменная app, её выражение вычисляется немедленно
3. В этом пространстве имён создаётся функция index()
4. Отрабатывает декоратор @app.route(), и регистрирует эту функцию index() как обработчик запроса. Обрати внимание, ни функция index(), ни @app.route понятия не имеют, что index() находится внутри класса! Поэтому app.route не ожидает, что index() будет первым параметром принимать какой-то там self. Как следствие, вместо self функция index() получит то, что передал первым параметром Flask. В данному случае - ничего, так как у тебя роут без параметров.
5. Ну и в итоге отрабатывает создание класса, с учётом указанных предков (у тебя это неявно указанный object), наполнения пространства имён, и пр. Этот класс потом присваивается переменной Web в вышележащем пространстве имён.

Ну и как теперь жить?
А очень просто. Если тебе вот позарезу нужны обработчики роутов в виде методов класса, можно сделать так:
class Web:
    def __init__(self):
        self.app = Flask(__name__) 
        # получаем обёртку для метода, привязанную к нашему self, см. пункт 2
        self_index = self.index
        # вызываем декоратор вручную, см. пункт 3.
        decorator = self.app.route('/')
        decorator(self_index)
        # ну или то же самое в одну строку:
        self.app.route('/')(self.index)

    def index(self):
        return render_template('index.html')

Разумеется, это не очень удобно. Можно попробовать схитрить, написав свой декоратор, который помечает методы класса ассоциированным роутом, а в конструкторе экземпляра найти все помеченные методы и скормить их фласку одним списком.
Но я почти уверен, что такой механизм уже кем-то реализован, просто надо поискать... ну или забить на класс, если от него нет выигрыша.
Ответ написан
Комментировать
Пригласить эксперта
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Войти через центр авторизации
Похожие вопросы