Flask多个URL处理程序部分处理URLs

2 投票
1 回答
2092 浏览
提问于 2025-04-18 05:45

在Flask中,是否可以定义一些网址路由,让它们处理网址的一部分,然后把剩下的部分传给下一个处理程序继续处理呢?

举个例子,假设网址中有一部分是静态的,重复出现,而且每次都需要做相同的处理。

比如说:

/user/1/something

还有:

/user/1/something-else

再比如:

/user/2/...

理想情况下,一个处理程序可以处理 /user/<id> 这一部分(比如从数据库中加载记录等),并把结果存储到本地上下文中。然后,另一个处理程序再处理剩下的部分。这种方式让我可以更换 user 这一部分(例如换成 /user/<name>),而不需要修改其他的路由。

在Flask中,这样做是否可行?如果可以,应该怎么做呢?

1 个回答

3

我不太确定你想要的是否能实现。不过,从你上面描述的用法来看,我觉得你只需要一个转换器。

什么是转换器?

它就像魔法一样,让你可以这样定义路由:

@app.route('/double/<int:number>')
def double(number):
    return '%d' % (number * 2)

在上面的例子中,这个路由只接受 /double/ints。更棒的是,传入的 URL 部分会被自动转换成整数。所以叫它转换器,哈哈。

转换器的格式是 <converter:variable_name>。你可以在这里了解 Flask 内置的转换器 这里

转换器最酷的地方是你可以为自定义数据类型 编写自己的转换器!只需从 werkzeug.routing.BaseConverter 继承,并实现 to_python 和 to_url 方法。下面,我为常见的用户对象创建了一个简单的转换器。

class UserConverter(BaseConverter):
    """Converts Users for flask URLs."""

    def to_python(self, value):
        """Called to convert a `value` to its python equivalent.

        Here, we convert `value` to a User.
        """
        # Anytime an invalid value is provided, raise ValidationError.  Flask
        # will catch this, and continue searching the other routes.  First,
        # check that value is an integer, if not there is no way it could be
        # a user.
        if not value.isdigit():
            raise ValidationError()
        user_id = int(value)

        # Look up the user in the database.
        if user_id not in _database:
            raise ValidationError()

        # Otherwise, return the user.
        return _database[user_id]

    def to_url(self, value):
        """Called to convert a `value` to its `url` equivalent.

        Here we convert `value`, the User object to an integer - the user id.
        """
        return str(value.id)

转换器什么时候被调用?

当 Flask 匹配到你的路由,并需要填充由转换器处理的部分时,就会调用转换器。所以,给定以下路由 - /find/<user:user>,下面所有的 URL 都会被我们的转换器处理。

  1. /find/1
  2. /find/2
  3. /find/jaime
  4. /find/zasdf123

对于上面的 URL,UserConverter.to_python 方法会被依次调用,参数是 '1'、'2'、'jaime' 和 'zasdf123'。这个方法需要判断这些值是否能转换成有效的用户。如果可以,合法的用户就会直接传递给路由的 user 参数。

不过,Flask 仍然需要 知道 你的新转换器。这很简单:

app.url_map.converters['user'] = UserConverter

自定义转换器的最后一个酷炫功能是,它们让构建 URL 变得自然且简单。要创建一个 URL,只需这样做:

url_for('find_user', user=user)

最后,这里有一个简单的程序,把所有这些内容结合起来。

from flask import Flask, url_for
from werkzeug.routing import BaseConverter, ValidationError


class User:

    def __init__(self, id, name):
        self.id = id
        self.name = name


class UserConverter(BaseConverter):
    """Converts Users for flask URLs."""

    def to_python(self, value):
        """Called to convert a `value` to its python equivalent.

        Here, we convert `value` to a User.
        """
        # Anytime an invalid value is provided, raise ValidationError.  Flask
        # will catch this, and continue searching the other routes.  First,
        # check that value is an integer, if not there is no way it could be
        # a user.
        if not value.isdigit():
            raise ValidationError()
        user_id = int(value)

        # Look up the user in the database.
        if user_id not in _database:
            raise ValidationError()

        # Otherwise, return the user.
        return _database[user_id]

    def to_url(self, value):
        """Called to convert a `value` to its `url` equivalent.

        Here we convert `value`, the User object to an integer - the user id.
        """
        return str(value.id)


# Create a `database` of users.
_database = {
    1: User(1, 'Bob'),
    2: User(2, 'Jim'),
    3: User(3, 'Ben')
}

app = Flask(__name__)
app.url_map.converters['user'] = UserConverter

@app.route('/find/<user:user>')
def find_user(user):
    return "User: %s" % user.name

@app.route('/find/<user:user>/extrapath')
def find_userextra(user):
    return 'User extra: %s' % user.name

@app.route('/users')
def list_users():
    # Return some broken HTML showing url's to our users.
    s = ''
    for user in _database.values():
        s += url_for('find_user', user=user) + '<br/>'
    return s

if __name__ == '__main__':
    app.run(debug=True)

如果有什么不清楚的地方,或者 哎呀 有 bug,请告诉我 :D。这与你的问题有关,因为你提到了一系列开头相同的 URL --

/user/1/something

/user/1/something-else

/user/2/

对于这些路由,你可以这样应用自定义转换器:

@app.route('/user/<user:user>/something')

@app.route('/user/<user:user>/something-else')

@app.route('/user/<user:user>/)

撰写回答