Чтобы понять, нужно понимать три вещи про питон.
Во-первых, в нём всё - оператор. Да-да.
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')
Разумеется, это не очень удобно. Можно попробовать схитрить, написав свой декоратор, который помечает методы класса ассоциированным роутом, а в конструкторе экземпляра найти все помеченные методы и скормить их фласку одним списком.
Но я почти уверен, что такой механизм уже кем-то реализован, просто надо поискать... ну или забить на класс, если от него нет выигрыша.