arangda 浏览28次 2020-02-14

并非魔术-Flask和@app.route-第1部分

我已经有一段时间没有发表文章了,所以我觉得是时候在我的博客上开始一个新的系列了。

这是我称之为“并非魔术”的系列的第一版,在这里我展示了流行的开源软件包提供的一些更好的API是如何从它们各自的语言的原语构造而成的。

在这篇文章中,我们将研究Flask,更具体地说,Flask是如何能够在函数的顶部写“@app.route()”并将它的结果暴露给internet的。

下面是在Flask的主页上给我们的第一个例子,为了更好的理解“@app.route()”是如何工作的,我们将解构第一个例子。

app = Flask(__name__)

@app.route("/")
def hello():
    return "Hello World!"

@app.route和其他装饰器

为了理解“@app.route()”是如何工作的,我们首先需要了解Python中的装饰器(以“@”开头并在函数的定义之前)。

装饰器到底是什么? 没什么特别的! 装饰器只是一个函数,它接受一个函数(用“@”符号装饰的那个函数)并返回一个新函数。

装饰函数时,是在告诉Python调用装饰器返回的新函数,而不是直接运行函数的主体。

还没有100%明白? 这是一个简单的示例:

# 这是我们的装饰器
def simple_decorator(f):
    # 这是我们将要返回的新函数
    # 此函数将代替原来的定义
    def wrapper():
        print("Entering Function")
        f()
        print("Exited Function")

    return wrapper

@simple_decorator 
def hello():
    print("Hello World")

hello()

运行上述代码将生成以下输出:

Entering Function
Hello World
Exited Function

现在,我们已经了解了如何构建自己的“@app.route()”装饰器,但是您可能已经注意到,我们的简单装饰器不接受任何参数,而“app.route()”接受参数。

那么如何将参数传递给装饰器呢? 为此,我们只需创建一个“装饰器工厂”函数(可以调用),然后将装饰器返回给我们的函数即可。来看看实际应用情况。

def decorator_factory(enter_message, exit_message):
    # 我们将返回这个装饰器
    def simple_decorator(f):
        def wrapper():
            print(enter_message)
            f()
            print(exit_message)

        return wrapper

    return simple_decorator

@decorator_factory("Start", "End")
def hello():
    print("Hello World")

hello()

将输出:

 Start
 Hello World
 End

注意,当我们写@decorator_factory("Start", "End")时,我们实际上是在调用函数decorator_factory,它返回实际使用的装饰器,很简洁,是吧?

将“app”放入“app.route”中

现在我们已经知道了为了重新实现Flask API的这一部分,装饰器是如何工作的,所以让我们把注意力转移到Flask应用程序中“app”的重要性上。

为了开始了解Flask对象内部的情况,我们将创建自己的Python类NotFlask。

class NotFlask():
    pass

app = NotFlask()

这不是一个非常有趣的类,但是要注意的是,类的方法也可以用作装饰器,因此,通过添加一个称为route的方法(这将是一个简单的装饰器工厂),使我们的类更有趣。

class NotFlask():
    def route(self, route_str):
        def decorator(f):
            return f

        return decorator

app = NotFlask()

@app.route("/")
def hello():
    return "Hello World!"

这个装饰器和我们之前创建的装饰器之间的主要区别是,我们不想修改正在装饰的函数的行为,我们只想引用它。

因此,最后一个技巧,我们在装饰器函数中存储给定路由和应该与其关联的装饰函数之间的链接。

为此,我们将“routes”字典添加到NotFlask对象,并且当调用“decorator”函数时,我们会将路由及其映射到的函数插入到新字典中。

class NotFlask():
    def __init__(self):
        self.routes = {}

    def route(self, route_str):
        def decorator(f):
            self.routes[route_str] = f
            return f

        return decorator

app = NotFlask()

@app.route("/")
def hello():
    return "Hello World!"

但如果没有办法访问视图函数,那么路由字典又有什么用呢?让我们添加一个方法serve(path),如果给定路由存在,它将给出运行该函数的结果;如果路由尚未注册,它将引发异常。

class NotFlask():
    def __init__(self):
        self.routes = {}

    def route(self, route_str):
        def decorator(f):
            self.routes[route_str] = f
            return f

        return decorator

    def serve(self, path):
        view_function = self.routes.get(path)
        if view_function:
            return view_function()
        else:
            raise ValueError('Route "{}"" has not been registered'.format(path))


app = NotFlask()

@app.route("/")
def hello():
    return "Hello World!"

在本系列中,我们只关注于复制流行库的漂亮api,因此实际上将“serve”方法连接到HTTP服务器超出了本文的范围,但是请放心,运行以下代码片段:

app = NotFlask()

@app.route("/")
def hello():
    return "Hello World!"

print app.serve("/")

将输出

Hello World!

我们已经重新实现了第一个非常简单的Flask网站管理的示例,因此让我们编写一些快速测试来检查对Flask“@app.route()”的重新实现是否正确。

class TestNotFlask(unittest.TestCase):
    def setUp(self):
        self.app = NotFlask()

    def test_valid_route(self):
        @self.app.route('/')
        def index():
            return 'Hello World'

        self.assertEqual(self.app.serve('/'), 'Hello World')

    def test_invalid_route(self):
        with self.assertRaises(ValueError):
            self.app.serve('/invalid')

只需要一个简单的装饰器和一个字典就可以在Flask中复制“app.route()”装饰器的基本行为。在本系列的下一篇文章中,也就是关于Flask的app.route()的最后一篇文章中,我们将通过分析下面的例子来了解动态URL模式是如何工作的。

app = Flask(__name__)

@app.route("/hello/<username>")
def hello_user(username):
    return "Hello {} !".format(username)

敬请期待!

本文翻译自https://ains.co/blog/things-which-arent-magic-flask-part-1.html, 翻译不当之处还望留言指正。

0 条评论 最新

还没有评论哦.