<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Bottle on Dapeng Zhang</title><link>https://zhangdapeng.dev/tags/bottle/</link><description>Recent content in Bottle on Dapeng Zhang</description><generator>Hugo -- gohugo.io</generator><language>en-us</language><lastBuildDate>Tue, 24 Jun 2025 11:24:13 +0000</lastBuildDate><atom:link href="https://zhangdapeng.dev/tags/bottle/index.xml" rel="self" type="application/rss+xml"/><item><title>[深度分析] Python Web 开发框架 Bottle</title><link>https://zhangdapeng.dev/p/shen-du-fen-xi-python-web-kai-fa-kuang-jia-bottle/</link><pubDate>Tue, 24 Jun 2025 11:24:13 +0000</pubDate><guid>https://zhangdapeng.dev/p/shen-du-fen-xi-python-web-kai-fa-kuang-jia-bottle/</guid><description>&lt;p&gt;&lt;a class="link" href="http://bottlepy.org/docs/dev-cn/" target="_blank" rel="noopener"
 &gt;Bottle&lt;/a&gt;是一个非常精致的WSGI框架，它提供了 Python Web开发中需要的基本支持：URL路由，Request/Response对象封装，模板支持，与WSGI服务器集成支持。整个框架的全部代码约有 2000行，它的核心部分没有其他任何依赖，只要有Python环境就可以运行。&lt;/p&gt;
&lt;p&gt;Bottle适用于小型的Web开发，在应用程序规模比较小的情况下可以实现快速开发。但是由于自身功能所限，对于大型的Web程序，Bottle的功能略显不足，程序员需要手动管理模块、数据库、配置等等，与Pylons等框架相比Bottle的优势就难以体现出来了。&lt;/p&gt;
&lt;h2 id="快速入门"&gt;&lt;a class="link" href="#1" &gt;快速入门&lt;/a&gt;
&lt;/h2&gt;&lt;p&gt;通过一个简单的、典型的例子描述Bottle的使用：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;from&lt;/span&gt; bottle &lt;span style="color:#f92672"&gt;import&lt;/span&gt; Bottle, run, mako_view, request
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;myapp &lt;span style="color:#f92672"&gt;=&lt;/span&gt; Bottle()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;@myapp.get&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#39;/hello/:name/:count#&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;\\&lt;/span&gt;&lt;span style="color:#e6db74"&gt;d+#&amp;#39;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;@mako_view&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#39;hello&amp;#39;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;def&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;hello&lt;/span&gt;(name, count):
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ip &lt;span style="color:#f92672"&gt;=&lt;/span&gt; request&lt;span style="color:#f92672"&gt;.&lt;/span&gt;environ&lt;span style="color:#f92672"&gt;.&lt;/span&gt;get(&lt;span style="color:#e6db74"&gt;&amp;#39;REMOTE_ADDR&amp;#39;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; dict(n&lt;span style="color:#f92672"&gt;=&lt;/span&gt;name, c&lt;span style="color:#f92672"&gt;=&lt;/span&gt;int(count), ip&lt;span style="color:#f92672"&gt;=&lt;/span&gt;ip)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;run(app&lt;span style="color:#f92672"&gt;=&lt;/span&gt;myapp)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;我们创建一个Bottle对象，通过decorator配置一条路由记录。Bottle的url映射支持具名参数，“/hello/:name/:count#\d+#” 格式的参数，可以匹配/hello/(.+?)/(\d+?)的 URL。在方法体中，通过environ字典获得客户端IP，这个操作和其他WSGI框架是一致的。接着通过一个字典类型将Model数据传递给 View。View模板通过decorator定义，采用mako模板引擎实现，模板名为hello，他表示在当前目录下一个名叫hello.tpl的文 件。&lt;/p&gt;
&lt;h2 id="剖析"&gt;&lt;a class="link" href="#2" &gt;剖析&lt;/a&gt;
&lt;/h2&gt;&lt;p&gt;Bottle主要可以分成4个模块：&lt;/p&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;Router和WSGI Application&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;Request和Response, Web helpers&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;Template Adapters&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;WSGI Server Adapters&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;
&lt;h2 id="router和wsgi-application"&gt;&lt;a class="link" href="#3" &gt;Router和WSGI Application&lt;/a&gt;
&lt;/h2&gt;&lt;p&gt;一个Bottle对象是一个标准的WSGI App，这使他可以与很多WSGI Server集成。每个app下管理一个Router用于针对URL映射到handler方法。根据Python decorator的特性，路由规则是在程序启动时自动执行的。&lt;/p&gt;
&lt;p&gt;Bottle的route方法用于注册handler到router中。除此以外，还有很多function currying帮助您简单地路由GET / POST 等特定HTTP方法。&lt;/p&gt;
&lt;p&gt;Bottle最近以来支持了hook，可以注册一些方法在每个请求之前、或之后执行。&lt;/p&gt;
&lt;p&gt;当接受一个HTTP请求时，WSGI Server会调用WSGI App的wsgi方法。它的流程是：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;根据标准的WSGI接口规范，从WSGI environ中生成一个Request对象，初始化一个response对象。&lt;/li&gt;
&lt;li&gt;在寻找方法之前，实际上Bottle会自动执行您注册的&lt;code&gt;before_hook&lt;/code&gt;。（&lt;code&gt;Bottle._add_hook_wrapper&lt;/code&gt;）&lt;/li&gt;
&lt;li&gt;根据request.path的URL在router中寻找对应的handler方法（Bottle.match_url, Router.match）。这一步中除了找到合适的handler方法，还要负责提取url中的具名参数，将结果以tuple的形式返回。&lt;/li&gt;
&lt;li&gt;以返回的方法和参数，执行handler，获得返回值。&lt;/li&gt;
&lt;li&gt;执行所有的after_hooks。&lt;/li&gt;
&lt;li&gt;根据返回的不同类型，写入Response的头部。在这一步，Bottle还会应用一些filter，在Bottle中，filter是用于处理 handler返回类型的。例如，一个典型的filter即内置的dict2json，他将handler返回的dict类型自动换成 json。（&lt;code&gt;Bottle._cast&lt;/code&gt;）&lt;/li&gt;
&lt;li&gt;根据返回的HTTP状态码，对handler返回对象进行处理。调用WSGI Server的start_response方法将返回对象写给客户端。&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="request和response-web-helpers"&gt;&lt;a class="link" href="#4" &gt;Request和Response, Web helpers&lt;/a&gt;
&lt;/h2&gt;&lt;p&gt;Bottle对HTTP的请求和响应封装了Request和Response对象。它采用threadlocal的方式，由Bottle app管理生命周期。您可以在声明handler方法时不必像Java Servlet那样将他们以参数传入，这样增加了方法设计的灵活性，也使得单元测试变得相对轻松。&lt;/p&gt;
&lt;p&gt;Request对象是对WSGI environ属性的封装，可以从中取得的属性取决于WSGI Server对&lt;a class="link" href="https://www.python.org/dev/peps/pep-0333/#environ-variables" target="_blank" rel="noopener"
 &gt;PEP333&lt;/a&gt;的实现。&lt;/p&gt;
&lt;p&gt;关于Request API，可以参考&lt;a class="link" href="http://bottle.paws.de/docs/dev/api.html#bottle.Request" target="_blank" rel="noopener"
 &gt;文档&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;类似的，可以参考&lt;a class="link" href="http://bottle.paws.de/docs/dev/api.html#bottle.Response" target="_blank" rel="noopener"
 &gt;Response API&lt;/a&gt;了解如何对HTTP响应进行操作&lt;/p&gt;
&lt;p&gt;和很多Web框架一样，你不必手动去设置HTTP重定向，helper方法会简化这些操作：&lt;code&gt;abort&lt;/code&gt;, &lt;code&gt;redirect&lt;/code&gt;, &lt;code&gt;static_file&lt;/code&gt;。当然，你也可以自己创建一些helper。&lt;/p&gt;
&lt;h2 id="template-adapters"&gt;&lt;a class="link" href="#5" &gt;Template Adapters&lt;/a&gt;
&lt;/h2&gt;&lt;p&gt;template方法用于渲染视图，您可以使用不同的模板实现：&lt;code&gt;mako&lt;/code&gt;, &lt;code&gt;jinja2&lt;/code&gt;, &lt;code&gt;cheetah&lt;/code&gt;, &lt;code&gt;simpletal&lt;/code&gt;以及Bottle自己的简单实现。&lt;/p&gt;
&lt;p&gt;view作为一个decorator可以简化模板的选择。与route类似，作者也提供了一些function currying来支持mako_view这样简便的写法。&lt;/p&gt;
&lt;h2 id="server-adapters"&gt;&lt;a class="link" href="#6" &gt;Server Adapters&lt;/a&gt;
&lt;/h2&gt;&lt;p&gt;Bottle的Server Adapters简直可以说是WSGI Server的博览会，从这里您可以了解目前比较流行的WSGI实现：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;flup&lt;/li&gt;
&lt;li&gt;wsgiref&lt;/li&gt;
&lt;li&gt;cherrypy&lt;/li&gt;
&lt;li&gt;paste&lt;/li&gt;
&lt;li&gt;fapws3&lt;/li&gt;
&lt;li&gt;tornado&lt;/li&gt;
&lt;li&gt;Google Appengine&lt;/li&gt;
&lt;li&gt;twisted&lt;/li&gt;
&lt;li&gt;diesel&lt;/li&gt;
&lt;li&gt;meinheld&lt;/li&gt;
&lt;li&gt;gunicorn&lt;/li&gt;
&lt;li&gt;eventlet&lt;/li&gt;
&lt;li&gt;gevent&lt;/li&gt;
&lt;li&gt;rocket&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;当然，这些不是全部，如果要使用不在其中的WSGI Server，您只需实现一个ServerAdapter的run方法即可，需要做的就是将Bottle app传给server并启动它。&lt;/p&gt;
&lt;p&gt;内置的run方法用于启动服务，您还可以指定一个reloader参数使Bottle在后台检查源文件的修改情况，实现热加载。&lt;/p&gt;
&lt;p&gt;除了run方式的启动，由于Bottle app本身就是一个符合标准的WSGI app，所以也可以通过一些服务器特有的方式启动服务，例如gunicorn：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;gunicorn -w &lt;span style="color:#ae81ff"&gt;2&lt;/span&gt; -D -b 127.0.0.1:18080 module:app
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="实战middlewares"&gt;&lt;a class="link" href="#7" &gt;实战Middlewares&lt;/a&gt;
&lt;/h2&gt;&lt;p&gt;Middleware是WSGI的重要概念http://www.python.org/dev/peps/pep-0333 /#middleware-components-that-play-both-sides 借助一些成熟的middleware可以添加一些Bottle目前不具备的功能：没错，Session。&lt;/p&gt;
&lt;p&gt;Session Middleware最著名的选择叫做Beaker，要其用Beaker，只需要一个装饰器模式的App声明即可，您可以参考Beaker的&lt;a class="link" href="http://beaker.groovie.org/configuration.html" target="_blank" rel="noopener"
 &gt;文档&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Pylons以使用Middleware著称，而除了Routing这样核心的Middleware，包括Beaker和Authkit都可以应用在Bottle程序上。&lt;/p&gt;
&lt;h2 id="app-mounting"&gt;&lt;a class="link" href="#8" &gt;App Mounting&lt;/a&gt;
&lt;/h2&gt;&lt;p&gt;Bottle的App提供一个很有用的mount方法帮助你将Web应用模块化。您可以将一个Bottle App挂载到一个父App上的某个路径，以父App启动后，父App可以为子App在一个路径下提供路由。&lt;/p&gt;
&lt;p&gt;不过你会遇到这样的问题：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;child &lt;span style="color:#f92672"&gt;=&lt;/span&gt; Bottle()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;@child.get&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;/&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;def&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;hello&lt;/span&gt;():
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;hello world&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;parent &lt;span style="color:#f92672"&gt;=&lt;/span&gt; Bottle()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;parent&lt;span style="color:#f92672"&gt;.&lt;/span&gt;mount(child, &lt;span style="color:#e6db74"&gt;&amp;#34;/child&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;很自然的，您希望打开浏览器访问http://localhost:8000/child可能看到hello world，然后却得到404.这个问题不难察觉，于是访问http://localhost:8000/child/，工作正常。&lt;/p&gt;
&lt;p&gt;我曾给作者提过这个问题http://github.com/defnull/bottle/issues#issue/88，（补一 句，Bottle的作者少有的Nice，对你提出的问题他通常都会认真解答），作者提了一个重定向的方式，不过还是没有决定把它直接加进mount方法 里。&lt;/p&gt;
&lt;p&gt;那么，我们需要一个新的mount来支持http://localhost:8000/child&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;def&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;mount&lt;/span&gt;(parent, child, path):
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; parent&lt;span style="color:#f92672"&gt;.&lt;/span&gt;mount(child, path)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;@parent.get&lt;/span&gt;(path)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;def&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;redir&lt;/span&gt;():
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; redirect(path&lt;span style="color:#f92672"&gt;+&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;/&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="google-appengine"&gt;&lt;a class="link" href="#9" &gt;Google AppEngine&lt;/a&gt;
&lt;/h2&gt;&lt;p&gt;因为有WSGI标准，你可以用Bottle开发Google AppEngine程序。您只需要在handler文件中加入这样的代码即可：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;from&lt;/span&gt; google.appengine.ext.webapp.util &lt;span style="color:#f92672"&gt;import&lt;/span&gt; run_wsgi_app
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;def&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;main&lt;/span&gt;():
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; run_wsgi_app(my_bottle_app)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; __name__ &lt;span style="color:#f92672"&gt;==&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;__main__&amp;#39;&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; main()
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;依赖？Bottle本身是self-contained，不过你可能还需要一个强大一些的模板引擎来完成你的应用，比如Mako。这样，你需要把依赖和自己的程序一起上传到GAE。对于Mako来说，除了它自己，还依赖marksave，别忘了它。&lt;/p&gt;
&lt;p&gt;以上就是我对Bottle的理解和使用心得，希望您在合适的场景下使用Bottle，只有这样，才能感受到这个框架所带来的乐趣。&lt;/p&gt;</description></item></channel></rss>