tornado表单和模板

在第一章中,我们学习了使用tornado创建一个web应用的基础知识。包括处理函数,HTTP方法以及tornado的框架的总体结构。在这一章中,我们学习tornado的更加强大的功能—-表单和模板。
和大多数的web框架一样,tornado的一个重要的目标就是帮助你更快地编写程序,尽可能整洁地复用更多的代码。尽管tornado灵活,可以使用几乎所有python支持的模板语言,tornado自身也提供了一个轻量级,快速并且灵活的模板语言,在tornado.template模块中。

简单示例 Poem Maker Pro

poemmaker.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
import os.path
import tornado.httpserver
import tornado.options
import tornado.web
import tornado.ioloop
from tornado.options import define, options
define("port", default=8000, help="run on the given port", type=int)
class IndexHandler(tornado.web.RequestHandler):
def get(self):
self.render("index.html")
class PoemPageHandler(tornado.web.RequestHandler):
def post(self):
noun1 = self.get_argument("noun1")
noun2 = self.get_argument("noun2")
verb = self.get_argument("verb")
noun3 = self.get_argument("noun3")
self.render("poem.html", roads=noun1, wood=noun2, made = verb, difference=noun3)
if __name__ == "__main__":
tornado.options.parse_command_line()
app = tornado.web.Application(handlers=[(r"/", IndexHandler),
(r"/poem", PoemPageHandler)],
template_path = os.path.join(os.path.dirname(__file__), "templates"),
static_path = os.path.join(os.path.dirname(__file__), "static"),
debug=True)
http_server = tornado.httpserver.HTTPServer(app)
http_server.listen(options.port)
tornado.ioloop.IOLoop.instance().start()

除了上述poemmaker.py,还需要在文件夹templates下添加以下两个文件。
/templates/index.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<!DOCTYPE html>
<html>
<head><title>Poem Maker Pro</title></head>
<body>
<h1>Enter terms below.</h1>
<form method="post" action="/poem">
<p>Plural noun <br/> <input type="text" name="noun1" /></p>
<p>Singular noun<br/> <input type="text" name="noun2" /></p>
<p>Verb (haha)<br /> <input type="text" name="verb" /></p>
<p>Noun<br/> <input type="text" name="noun3" /> </p>
<input type="submit"/>
</form>
</body>
</html>

/templates/poem.html

1
2
3
4
5
6
7
8
9
10
11
12
<!DOCTYPE html>
<html>
<head><title>this is the poem</title></head>
<body>
<h1>Your poem</h1>
<p>Two {{roads}} diverged in a {{wood}}, and I—<br/>
I took the one less tracelled by, <br/>
and that has {{made}} all the {{difference}}.</p>
</body>
</html>

运行python文件:

1
python poemmaker.py

此时访问8000端口会出现首页上包括多个表单等待用户输入。这个表单包括多个文本域(命名为noun1, noun2等),其中内容将在用户点击“submit”按钮时以post请求的方式发送到/poem。
当点击submit按钮之后,tornado应用跳转到poem.html,插入你在表单中填写的值。结果是显示的一首诗。

下面我们逐条讲述当前示例中的每一个涉及到的知识点:

2.1.1渲染模板

从结构上讲,poemmaker和第一章的例子很相似。我们定义了几个RequestHandler子类并把它们传递给tornado.web.Application对象。那么有什么不一样的地方呢?首先我们向Application对象的init方法传递了一个template参数。

1
template_path = os.path.join(os.path.dirname(__file__), "templates")

template_path参数告诉tornado在哪里寻找模板文件。我们将在第三章中讲解其确切的用法和性质。而它的基本要点是:模板是一个允许你嵌入python代码片段的html文件。上面的代码告诉python在你的tornado应用文件相同目录下的templates文件夹下寻找模板文件。
一旦我们告诉tornado在哪里寻找模板,我们就可以使用RequestHandlser类的render方法来告诉tornado读入模板文件,插入其中的模板代码,并将结果返回给浏览器。比如,在IndexHandler中,我们可以发现语句:

1
self.render("index.html")

这段代码告诉tornado载templates文件夹下找到一个名字为index.html的文件,读取其中的内容,并发送给浏览器。

2.12 填充

实际上index.html完全不能称之为模板,因为它所包含的完全是已经编写好的html标记。这可以是模板一个不错的使用方式。但是在通常情况下我们更希望html输出可以结合我们的程序传入给模板的值。模板poem.html使用PoemPageMaker渲染,它是一个模板。
我们可以看到,在poem.html中,它可以看到模板中有一些被双大括号括起来的字符串,就像这样:

1
2
3
<p>Two {{roads}} diverged in a {{wood}}, and I—<br/>
I took the one less tracelled by, <br/>
and that has {{made}} all the {{difference}}.</p>

在双大括号中的单词是占位符,当我们渲染模板时希望以实际值代替。我们可以使用render函数中传递关键字参数的方法 指定什么值将被填充到html文件中的对应位置,其中关键字对应模板文件中占位符的名字。下面是在poemPageMaker中相应的代码部分:

1
2
3
4
5
noun1 = self.get_argument("noun1")
noun2 = self.get_argument("noun2")
verb = self.get_argument("verb")
noun3 = self.get_argument("noun3")
self.render("poem.html", roads=noun1, wood=noun2, made = verb, difference=noun3)

在这里,我们告诉模板使用变量noun1作为模板中roads的值,以此类推。

2.13 设置静态路径

你可以通过向Application类的构造函数传递一个名为static_path的参数来告诉tornado从文件系统一个特定的位置提供静态文件。
在上述代码中如下:

1
static_path = os.path.join(os.path.dirname(__file__), "static"))

在这里,我们设置了一个当前应用目录下名为static的子目录作为static_path的参数。应用将读取static中的文件作为静态文件

2.1.4使用static_url生成url

tornado模板提供了一个叫做static_url的函数生成static目录下文件的url.让我们来看看在index.html中static_url的调用代码的示例代码:

1
<link rel="stylesheet" href="{{static_url("style.css")}}">

这个对static_url的调用生成了url的值,并渲染出类似下面的代码:

1
<link rel="stylesheet" href="/static/style.css?v=ab12>

那么为什么使用static_url而不是在你的模板中硬编码呢?原因有以下两点:
第一,static_url函数创建了一个基于文件内容的hash值,并将其添加到url末尾(查询字符串的参数是v).这个hash值确保浏览器总是加载一个文件的最新版本而不是以前的缓存版本。无论是在开发阶段还是部署到生产环境,都非常有用,因为用户不必为了可到你的静态内容去清除浏览器的缓存了。
第二,你可以改变你的url结构,而不用改变模板中的代码。例如,你可以配置tornado响应来自路径/s/filename的请求时提供静态内容,而不是默认的/static路径。如果你使用static_url而不是硬编码,这个时候你不需要改变代码中的值。

debug参数

在这个例子中,你可能注意到了debug=True的使用。它调用了一个便利的测试模块:tornado.autoreload模块,此时,一旦主要的python文件被修改,tornado会尝试重启服务器,而且载模板改变的时候会自动刷新。对于快速改变和实时更新这非常棒,但不要在生产上使用它,因为他将防止tornado缓存模板。

2.2模板语法

既然我们已经看到了一个模板在实际应用中的例子,哪买让我们深入了解它们是如何工作的。tornado模板是被python表达式和控制语句标记的简单文本文件。tornado的语法非常直接。在2.1节中,我们展示了如何在一个web应用中使用render方法传送html给浏览器。你可以在tornado应用之外使用python解释器导入模块尝试模板系统,此时结果会被直接输出出来。

1
2
3
4
>>> from tornado.template import Template
>>> content = Template("<html><body><h1>{{header}}</h1></body></html>")
>>> print content.generate(header="Welcome!")
<html><body><h1>Welcome!</h1></body></html>

2.21填充表达式

上面的演示我们也可以看出,向html中填充python变量的值,我们可以使用双大括号;其实我们可以在双大括号中插入python表达式,它会自动计算出其实际值:

1
2
3
4
5
6
7
>>> from tornado.template import Template
>>> print Template("{{ 1+1 }}").generate()
2
>>> print Template("{{ 'scrambled eggs'[-4:] }}").generate()
eggs
>>> print Template("{{ ', '.join([str(x*x) for x in range(10)]) }}").generate()
0, 1, 4, 9, 16, 25, 36, 49, 64, 81

2.22 控制流语句

同样也可以在tornado模板中使用python条件和循环语句。控制与句以{ %和% }包围,并以类似下面形式使用:

1
2
3
{% if page is None %}
{% if len(entrise) == 3 %}

控制语句的大部分就像对应的python语句一样工作,支持if,for,wihle,try。在这些情况下,语句块以{ %开始,以 % }结束

模板:

1
2
3
4
5
6
7
8
9
10
11
12
13
<!DOCTYPE html>
<html>
<head><title>{{title}}</title></head>
<body>
<h1>{{header}}</h1>
<ul>
{%for book in books%}
<li>{{book}}</li>
{% end %}
</ul>
</body>
</html>

处理函数:

1
2
3
4
class BookHandler(tornado.web.RequestHandler):
def get(self):
self.render("books.html", title="Home Page", header="Books that are great",\
books=["python", "c++", "pascal"])

这个时候将渲染出:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<!DOCTYPE html>
<html>
<head><title>{{title}}</title></head>
<body>
<h1>{{header}}</h1>
<ul>
<li>python</li>
<li>C++</li>
<li>pascal</li>
{% end %}
</ul>
</body>
</html>

不像其他的python模板渲染系统,tornado模板语言的一个最好的东西是在if和for语句块中可以使用的表达式没有限制.因此,你可以在你的模板中执行所有的python代码。
同样,你也可以在你的控制语句块中间使用{ % set foo = “bar” % }来设置变量的值。你还有很多可以在控制语句块中做的事情,但是,在大多数情况下,你最好使用UI模块来做更复杂的划分。我们稍后会更详细的看到这一点。

2.2.3在模板中使用函数

tornado在所有模板中默认提供了一些遍历的函数。他们包括:

1
2
3
4
5
6
7
8
#替换字符串s中的&,<,>为他们对应的html字符
escape(s)
# 使用urllib.quote_plus替换字符串s中的字符为URL编码形式
url_escape(s)
#将val编码成json格式,底层使用json.dumps()
json_encode(val)
#过滤字符串s,把连续的多个空白字符替换成一个空格
squeeze(s)

在模板中使用一个你自己编写的函数也很简单:只需要将函数名作为模板的参数传递即可,就像其他的变量。

1
2
3
4
5
6
7
8
9
>>> from tornado.template import Template
>>> def disemvowel(s):
... return ''.join([x for x in s if x not in 'aeiou'])
...
>>> disemvowel("george")
'grg'
>>> print Template("my name is {{d('mortimer')}}").generate(d=disemvowel)
my name is mrtmr

Comment and share

tornado入门

tornado是使用python编写的一个强大的,可扩展的web服务器。它在高并发的网络请求中表现的足够稳定,但是却在创建和编写的过程中有着足够的轻量级,并能够被用在大量的应用和工具中。
不同于那些最多只能达到10000个并发连接的传统web服务器,tornado在设计之初就考虑到了性能因素,旨在解决C10K问题,这样的设计使得其成为一个非常高性能的框架。此外,它还拥有处理安全性,用户验证,社交网络以及与外部服务进行异步交互的工具。

Hello tornado

tornado是一个编写对HTTP请求响应的框架。作为程序员,你的工作是编写响应特定条件HTTP请求的响应的handler。

hello.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import tornado.httpserver
import tornado.ioloop
import tornado.options
import tornado.web
from tornado.options import define, options
define("port", default=8000, help = "run on the give port", type = int)
class indexHandler(tornado.web.RequestHandler):
def get(self):
greeting = self.get_argument("greeting", "hello")
self.write(greeting + ", friend user!")
if __name__ == "__main__":
tornado.options.parse_command_line()
app = tornado.web.Application(handlers=[(r"/", indexHandler)])
http_server = tornado.httpserver.HTTPServer(app)
http_server.listen(options.port)
tornado.ioloop.IOLoop.instance().start()

编写一个tornado应用中最多的工作是定义继承自tornado.RequestHandler的类。在这个例子中,我们创建了一个简单的应用,在指定的端口监听请求,并在根目录(“/“)下响应请求。
执行命令:

1
python hello.py --port=9999

使用命令对其访问

1
2
3
4
5
curl http://localhost:9999
hello, friend user!
curl http://localhost:9999/?greeting=david
david, friend user!

现在tornado程序就已经在localhost:9999处进行监听。我们可以对其进行根目录使用get方法进行那个访问.

程序分析

下面我们开始逐条语句进行分析:
开始的import语句导入了程序所必须的tornado的相关模块。虽然tornado还有其他模块,但是我们在这个程序中只需要这四个模块

tornado中options模块分析

1
2
from tornado.options import define, options
define("port", default=8000, help="run on the given port", type=int)

tornado包含了一个有用的模块tornado.options来从命令行读取设置。我们在这里使用这个模块指定我们监听http请求的端口。它的工作流程是:
如果一个与define语句中同名的设置在命令行中给出,那么它将成为全局options的一个属性。如果用户程序在运行的时候使用了–help选项,程序将打印出你所定义的选项以及你在define函数的help参数中指定的文本。如果程序没有为这个选项指定值,那么则使用default进行替代。tornado使用type来验证参数的类型,当类型不合适的时候跑出一个异常。因此,我们允许一个整数port作为options.port来访问程序,如果用户没有指定port的值,则默认为9999端口。

请求处理函数类及方法

1
2
3
4
class indexHandler(tornado.web.RequestHandler):
def get(self):
greeting = self.get_argument("greeting", "hello")
self.write(greeting + ", friend user!")

这是tornado请求处理函数类,它继承自tornado.web.RequestHandler,当处理一个请求的时候,tornado将这个类实例化,并调用http请求方法所对应的方法。在这个例子中,我们只定义了get方法,也就是说这个类对http的GET请求作出响应。我们稍后将看到实现不止一个http方法的处理函数。

获取查询参数

1
self.get_argument("greeting", "hello")

tornado的request类有很多有用的内建函数,包括get_argument()用来用一个查询字符串中获取参数的值。上述的意思是从查询字符串中获取查询参数greeting的值,若查询参数中没有greeting参数,则会其值为”hello”,即为其默认值。

RequestHandler的write函数

1
self.write(greeting + ", firendly user")

tornado另一个有用的方法是write,它以一个字符串作为参数,并将其写入到http响应中去。在这里,我们使用请求中greeting的参数提供的值插入到greeting中,并写回到相应中去。

创建Application实例

1
2
3
if __name__ == "__main__":
tornado.options.parse_command_line()
app = tornado.web.Application(handlers=[(r"/", indexHandler)])

这里是真正使得tornado运转起来的语句。首先我们使用tornado的options解析命令行。然后我们创建了一个tornado的Application实例。传递给Application的init方法的最重要的参数就是handlers。它告诉tornado用那个类来响应特定的请求。

端口监听

1
2
3
http_server = tornado.httpserver.HTTPServer(app)
http_server.listen(options.port)
tornado.ioloop.IOLoop.instance().start()

从这离开始的代码会被反复使用,一旦Application对象被创建,我们可以将其传递给httpServer的对象,然后使用我们在命令行指定的端口进行监听。最后,在程序准备好接收HTTP请求后,我们创建一个tornado的IOLoop的实例进行启动。

简单字符串服务

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import textwrap
import tornado.web
import tornado.httpserver
import tornado.ioloop
import tornado.options
from tornado.options import define,options
define("port", default=8000, help="run on the given port", type=int)
class ReverseHandler(tornado.web.RequestHandler):
def get(self, input1, input2):
self.write(input1)
self.write(input2)
class WrapHandler(tornado.web.RequestHandler):
def post(self):
text =self.get_argument("text")
width = self.get_argument("width", 40)
self.write(textwrap.fill(text, int(width)))
if __name__ == "__main__":
tornado.options.parse_command_line()
app = tornado.web.Application(handlers=[(r"/reverse/([0-9]+)(\w+)", ReverseHandler),\
(r"/wrap", WrapHandler)])
http_server = tornado.httpserver.HTTPServer(app)
http_server.listen(options.port)
tornado.ioloop.IOLoop.instance().start()

上述是一个简单的字符串操作的web服务,有两个作用:第一,到/reverse/string的GET请求奖会返回url路径中指定字符串的反转形式。

1
2
$curl http://localhost:8000/reverse/123456
654321

作用二是,到/wrap的POST请求从参数text中取得指定文本,并返回按照参数width指定宽度装饰的文本。下面的请求指定一个没有宽度的字符串,所以它的输出宽度被指定为程序中的get_argument的默认值40

1
2
3
$http://localhost:8000/wrap -d text=Lorem+ipsum+dolor+sit+amet,+consectetuer+adipiscing+elit.
Lorem ipsum dolor sit amet, consectetuer
adipiscing elit.

RequestHandler的更多知识

HTTP方法

到目前为止我们只用了http中的get,post方法,但tornado支持任何合法的HTTP请求(GET,POST,HEAD,DELETE,OPTIONS)。你可以非常容易的定义上述任何一种方法的行为,只需要在RequestHandler类中使用同名方法。

HTTP状态码

从上面的代码可以看出,你可以使用RequestHandler类的set_status()方法显式的去设置http的状态麻,但是你需要记住在某些情况下,tornado会自动的设置状态码。下面是一种常见的纲要。

  • 404 Not Found
    当http请求的路径无法匹配到任何RequestHandler类对应的模式的时候tornado会返回404相应码。
  • 400 Bad Request
    如果你调用了一个没有默认值的get_argument()方法,并且没有发现给定名称的参数,tornado将自动返回一个400响应码
  • 405 Method Not Allowed
    如果传入的请求使用了RequestHandler中没有定义的http方法,tornado将自动返回一个405响应码。
  • 500 Internal Server Error
    当程序遇到任何不能让其退出的错误的时候,tornado自动返回响应码500.代码中任何没有捕获的异常也会返回500.
  • 200 OK
    如果相应成功,并没有其他返回码被设置,tornado将默认返回一个200.

Comment and share

迭代器和简单的生成器

摘自: https://www.ibm.com/developerworks/cn/linux/sdk/python/charm-20/

python2.2引进了一种带有新型关键字的新型构造。这个构造是生成器;关键字是yield.生成器使几个新型,强大和富有表现力的编程习惯成为可能,但初看,要理解生成器,还是有一点难度。本文由浅入深的介绍了生成器,同时还介绍了迭代器的相关问题。

由于迭代器比较容易理解,让我们先看它。基本上迭代器是含有.next方法的对象。这样定义不十分正确但非常接近。事实上,当迭代器应用新的iter()内置函数的时候,大多数迭代器的上下文希望返回迭代器,需要使iter()方法返回self.本文的示例将会说明清楚这一点。如果迭代有一个逻辑的终止,则迭代器的.next()方法可能决定抛出StopIteration异常。
生成器要稍微复杂化和一般化一点。但生成器典型的用途是用来定义迭代器;所以不值得总是为一些细微之处而担心,生成器是这样一个函数它记住上一次返回时在函数体中的位置,对于生成器函数的第二次调用跳转到该函数的中间,而上次调用的所有的局部变量都被记住

随机遍历

让我们考虑一个简单的问题,可以使用很多方法解决它。假设我们想要一串正的随机数字流,这个数字流的要求是每一个数字不允许小于0.1,且相邻两个数字之间的大小相差绝对值不小于0.4

版本一

版本一是比较传统的做法,我们直接写一个随机产生一串数字流的函数,并将他保存在一个list中返回:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
def generate_random():
last, rand = 1, random.random()
num = []
while rand > 0.1:
if abs(last - rand) >= 0.4:
last = rand
num.append(rand)
else:
print "invalid"
rand = random.random()
num.append(rand)
return num
for item in generate_random():
print item

从上面可以产出,这种方法有很大的局限性。首先这个示例中不可能产生庞大的数字列表,通过对数字的大小限制进行终结随机数的产生,我们可以通过限制进行预测此数列的大小。另一方面,在内存和性能方面使这种方法变得不切实际,以及没有必要。同样是这个问题,使得python较早的版本中添加了xrange()和xreadlines()。重要的是,许多流取决于外部事件,并且当每个元素可用时,才处理这些流。
在python2.1和较早的版本中,我们的诀窍是使用静态函数局部变量来记住函数上一次调用的事情。显而易见,全局变量可以用来做同样的工作,但是它会带来大家熟知的问题:命名空间污染问题

###版本二 使用静态成员解决上述问题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
def generate_random_static(last = [1]):
rand = random.random()
if rand < 0.1:
return
while abs(rand - last[0]) < 0.4:
print "invalid"
rand = random.random()
last[0] = rand
return rand
num = generate_random_static()
while num is not None:
print num
num = generate_random_static()

这个函数是一个很好的存储器,它只需要记住上一次的的值,返回一个单一的数字。而不需要存储一整个列表。并且与此类似的函数可以返回却绝育外部事件的连续的值。不方便的是使用这个函数不够方便,且想当不灵活

版本三 定义迭代器类

实质上python2.2的序列都是迭代器,python常见的习惯用法for elem in lst:而实际上是让;lst差生一个迭代器。然后for循环调用这个迭代器的.next()方法,直到遇到StopIteration为止。幸运的是,由于常见的内置类型自动产生它们的迭代器,所以python程序员不需要直到这里发生了什么。实际上,现在字典里有.iterkeys(),.iteritems(),.itervalues()方法产生迭代器。定制类支持直接使用randomwalk_list()以及一次一个元素这种极度节省的randomwalk_static,这是简单易懂的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class generate_random_class(object):
def __init__(self):
self.last = 1
self.rand = random.random()
def __iter__(self):
return self
def next(self):
if self.rand < 0.1:
raise StopIteration
else:
while abs(self.last - self.rand) < 0.4:
print "invalid"
self.rand = random.random()
self.last = self.rand
return self.rand
for item in generate_random_class():
print item

版本四 生成器版本

上述方法也会产生较多的问题,虽然它避免了产生整个list,大量局部变量的情况,但是当迭代器类或者函数取决于多个数据状态的时候,就需要记录多个数据的状态,这种情况下出现错误的记录较大。所以也不是一种较好的解决方法。使用python自带的生成器yield关键字
在 Python 2.2+ 中,不直接 写生成器。 相反,编写一个函数,当调用它时,返回生成器。这可能看起来有点古怪,但“函数工厂”是 Python 的常见特性,并且“生成器工厂”明显是这个概念性扩展。在 Python 2.2+ 中使函数成为生成器工厂是它主体某处的一个或多个 yield 语句。如果 yield 发生, return 一定只发生在没有伴随任何返回值的情况中。 然而,一个较好的选择是,安排函数体以便于完成所有 yield 之后,执行就“跳转到结束”。但如果遇到 return ,它导致产生的生成器抛出 StopIteration 异常,而不是进一步生成值。

1
2
3
4
5
6
7
8
9
10
11
def generate_random_yield():
last, rand = 1, random.random()
while rand > 0.1:
if abs(last - rand) >= 0.4:
last = rand
yield rand
rand = random.random()
yield rand
for item in generate_random_yield():
print item

上述做法的好处是逻辑简洁,与正常写法无意,也保证了通用性。

Comment and share

python yield使用浅析

摘自:https://www.ibm.com/developerworks/cn/opensource/os-cn-python-yield/
初学python的人经常会发现python函数中使用了很多yield关键字,然而,带有yield关键字的函数的执行流程和普通函数的执行流程不同,yield带来了什么作用,为什么要设计yield?本文将由浅入深地讲解yield的概念和用法,帮助读者体会yield在python中的简单而强大的功能
您可能听说过,带有yield的函数在python中称为generator(生成器),何谓生成器?先抛开generator,以一个常见的编程题目来展示yield的概念。

如何生成斐波那契数列

版本1(直接输出斐波那契数列)

斐波那契额数列的概念搭建应该比较清晰,相信大家能共很轻易的写出如下的算法来计算斐波那契数列:

1
2
3
4
5
6
def fab(max):
n, a, b = 0, 0, 1
while n < max:
print b
a, b = b, a + b
n = n + 1

执行fab(5),我们会的到如下的结果:

1
2
3
4
5
6
>>> fab(5)
1
1
2
3
5

结果没有问题,但是有经验的开发者会指出,直接在fab函数中 print打印出结果可复用性较差,因为fab返回的结果是None,其他函数无法获取该函数生成的斐波那契数列。所以要提高该函数的可复用性,最好不要直接打印出数列,而是返回一个list

版本二(返回list)

1
2
3
4
5
6
7
8
9
def fab(max):
n, a, b = 0, 0, 1
L = []
while n < max:
L.append(b)
a, b = b, a + b
n = n + 1
return L

使用如下方式打印出斐波那契数列:

1
2
3
4
5
6
7
>>> for n in fab(5):
... print =n
1
1
2
3
5

上述版本获取了可复用性的要求,但是该函数在运行的过程中占用的内存会随着参数max的增大而增大,如果要控制内存占用,最好不要用list来保存中介按结果,而是通过iterable对象来迭代。例如在python2.x中

1
2
3
for i in range(0,100)
for i in xrange(0,100)

前者会生成一个长度为100的list,而后者则不会生成一个100的list,而是在每次迭代中返回下一个数值,内存占用空间很小。因为xrange不返回list,而返回一个iterable的意向,利用iterable我们可以吧fab函数写成一个支持iterable的class

版本三(实现支持iterable的对象)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class fab(object):
def __init__(self, max):
self.max = maxself.n, self.a. self.b = 0, 0, 1
def __iter__(self):
return self
def next(self):
if self.n < self.max:
r = self.b
self.a, self.b = self.b, self.a + self.b
self.n = self.n + 1
return r
raise StopIteration

Fab函数通过next不断返回数列的下一个数,内存占用始终为常数

1
2
3
4
5
6
7
>>>for n in fab(5):
... print n
1
1
2
3
5

上述代码虽然实现了我们版本二的要求,但是代码远远没有第一个版本简洁。如果想要保持第一版的简洁,这个时候就要用上yield

版本四

1
2
3
4
5
6
def fab(max):
n, a, b = 0, 0, 1
while n < max:
yield b
a, b = b, a + b
n = n + 1

第四个版本和第一个版本相比仅仅把print b该成了yield b,就在保持简洁性的同时获得了iterable的效果。调用第四个版本和第二个版本的fab完全一致:

1
2
3
4
5
6
7
>>>for n in fab(5):
... print n
1
1
2
3
5

简单的将,yield的作用就是把一个函数变成了一个generator,带有yield的函数不在是一个普通函数,python解释器会将其视为一个generator,调用fab(5)不会执行fab函数,而是返回一个iterable对象。在for循环执行的时候,每次循环都会执行fab内部的代码,执行到yield b的时候,fab就返回一个迭代之,下次迭代时,代码从yieldb 的下一条语句执行,而函数的本地变量看起来和上次中断执行前是完全一样的,于是函数继续执行,直到再次遇到yield
也可以调用fab(5)的next()方法进行回去每次计算的值。

yield函数的执行流程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
>>> f = fab(5)
>>> f.next()
1
>>> f.next()
1
>>> f.next()
2
>>> f.next()
3
>>> f.next()
5
>>> f.next()
Traceback (most recent call last):
File "<stdin>". line 1, in <module>
StopIteration

当函数执行结束的时候,generator自动自动抛出StopIteration的异常,表示迭代的结束,而在for循环中,我们不需要手动的进行处理异常,循环会自动的正常结束。

一个带有yield的函数就是一盒generator,它和普通的函数不同,声称一个generator看起来想函数调用,但是部执行任何函数代码,直到对其调用next()(注意在for循环中会自动调用next)才开始执行。虽然执行流程和普通函数一样,但是每执行到一个yield语句,就会中断,并返回一个迭代值,下次执行的时候从yield的下一个语句开始执行。看起来像是一个函数在正常执行的过程中被yield中断了数次,每次中断都会通过yield返回当前迭代器的值。
yield的好处显而易见,把一个函数该写成generator就获得了迭代能力,比起在类的实例中保存状态计算下一个next的值,更加使代码清洁,而且执行流程非常清晰

判断是否为generator

方法是使用isgeneratorfunction来进行判断

1
2
from inspect import isgeneratorfunction
isgeneratorfunction(fab)

注意fab不可迭代,而fab(5)可迭代

return的作用

在一个generator function中,若函数中没有return语句,默认为函数执行到函数结尾,而如果中间遇到return语句,则直接判处StopIteration异常,结束迭代。
使用yield进行读取文件的例子

1
2
3
4
5
6
7
8
9
def read_file(fpath):
block_size = 1024
with open(fpath, "rb") as f:
while True:
block = f.read(block_size)
if block:
yield block
else
return

上述当文件读取完毕的时候奖直接返回StopIteration异常,结束迭代。

Comment and share

python装饰器

预备知识

一级对象

python将一切(包括函数)视为object的子类,即一切皆为对象,因此函数可以像变量一样被指向和传递,下面我们来看一个例子。

1
2
3
4
def foo():
pass
#注意issubclass是python的一个内置函数,用于判断两个类是不是子类关系
print issubclass(foo.__class__, object)

输出结果:

1
True

上述代码说明了python中的函数都是object的子类,下面我们看一下函数被当做参数传递的效果

1
2
3
4
5
6
7
def foo(func):
func()
def bar():
print "bar"
foo(bar)

运行结果如下:

1
bar

python中的作用域 namespace

python 提供namespace来重新实现函数/方法,变量等信息的区别,七一共有三种作用域:

  • local namespace:作用范围为当前函数或者类方法
  • global namespace:作用范围为当前模块
  • build-in namespace:作用范围为所有模块

当变量或者方法出现重名的情况时,python会按照local->global->build-in的顺序去搜索,并以第一个找到的元素为当前的namespace,此种做法与C/C++中的相似

*args和**kwargs

在python中我们使用*args和kwargs传递可变长参数,*args用作传递非命名键值可变长参数列表(位置参数);**kwargs用作传递键值可变长参数

  • *args:把所有的参数按照出现顺序打包成一个list
  • **kwargs:把所有的key-value形式的参数打包成一个dict

例子:

1
2
3
4
5
6
7
def add(x, y):
print x + y
params_list = (1, 2)
dict_list = {"x":1, "y":2}
add(*params_list)
add(**dict_list)

打印结果:

1
2
3
3

python装饰器入门

python允许你,作为程序员,使用函数完成一些很酷的事情。在python中,函数是一级对象(first-class),这就意味着你可以像使用字符串,整数,或者其他对象一样使用函数。例如,你可以将函数赋值给变量:

1
2
3
4
5
def square(n):
return n * n
print square(4) #16
alias = square
print alias(5) #25

然而一等函数的真正威力在于你可以把函数传给其他函数,或者从其他函数中返回函数。函数的内值函数map利用了这种能力:给map传一个函数以及一个列表,他会依次以列表中的每个元素作为参数调用你传给它的函数,从而生成一个新的列表。如下所示的例子中应用了上面的square函数:

1
2
number = [1,2,3,4]
print map(square, number) #[1,4,9,16]

如果一个函数接受一个函数作为参数或者返回一个函数,则这个函数被称为高阶函数虽然map简单使用了我们传给它的函数,而没有改变这个函数,但我们也可以使用高阶函数去改变其他函数的行为。
例如:
假设有这样一个函数,会被调用很多次,以致运行待解非常昂贵:

1
2
def fib(n):
return n if n in [0,1] else fib(n - 2) + fib(n - 1)

为了提高这个函数的效率,我们一般会保存计算过程中得出的中间结果,这样对于函数调用树中经常出现某个n,当需要计算n对应的结果时,就不需要重复计算了。有很多种方式可以实现这一点。例如我们可以将这些结果存在一个字典中,当某个值为参数调用fib函数的时候,首先去字典中查一下结果是否已经计算出来,如果计算出来直接返回反之计算。
但是这样的话,每次我们想调用fib函数,都需要重复那段相同的字典检查样板式代码。相反,如果让fib函数自己在内部负责存储结果,那么在其他代码中调用fib,就非常方便,只要简单的调用它就好了。这种技术被称为memoization
当然我们可以把这种memozition代码直接放入fib函数中,但是python给我们提供了一种更加优雅的选择因为可以编写修改其他函数的函数,那么我们就可以便携一个通用的memozation函数,以一个函数作为参考,并返回这个函数的memozation版本。

1
2
3
4
5
6
7
8
9
def memoze(fn):
store_results = {}
def memoized(args):
try:
return store_results[args]
except:
result = store_result[args] = fn(args)
return result
return memoized

如上,momoize函数以另一个函数作为参数,函数体中创建了一个字典对象来存储函数调用结果:键为被memoized包含后的函数的参数,值为以键为参数调用函数的返回值。memoize函数返回一个新的函数,这个函数会首先检查store_results中是否存在与当前参数对应的条目,如果有则直接返回,如果没有,则使用原始函数进行计算。memoize返回的这种新的函数常被称为包装器函数。因为它只是另外一个真正起作用的函数外面的一个薄层。
很好,我们现在有了memoize函数,现在将fib函数传给它,从而得到了一个经过包装的fib,这个版本的fib函数不需要重复以前的那样繁重的工作:

1
2
3
4
def fib(n):
return n if n in [0, 1] else fib(n - 2) + fib(n - 1)
fib = memoize(fib)

通过高阶函数memoize,我们获得了memoization带来的好处,并不需要对fib函数自己做出任何的改变,以免夹杂着memoization的代码而模糊了函数的实质工作。但是,你也许注意到上面的代码看着还是有点别扭,因为我们必须写三遍fib。由于这种模式(传递一个函数给另一个函数,然后将结果返回给与原来那个函数同名的函数变量,在使用包装器函数的代码中极为常见),python提供了一种特殊的愈发:装饰器

1
2
3
@memoize
def fib(n):
return n if n in [0, 1] else fib(n - 2) + fib(n - 1)

这里我们说memoize函数装饰了fib函数。需要注意的是这仅是语法上的简便写法(被称为“语法糖”)。这段代码与前面的代码片段做的是同样的事情:定义一个名为fib的函数,把它传给memoize函数,将返回结果存为名为fib的函数变量。特殊的(看起来有点奇怪的)@语法只是减少了冗余。

你可以使用多个装饰器,它会自底向上逐个起作用,例如假如有另外一个装饰器函数decorate

1
2
3
4
@memoize
@decorate
def fib(n):
return n if n in [0, 1] else fib(n - 2) + fib(n - 1)

等价于:

1
2
3
def fib(n):
return n if n in [0, 1] else fib(n - 2) + fib(n - 1)
fib = memoize(decorator(fib))

当装饰器函数含有参数的时候,方法是

1
2
3
@mimoize("172.168.1.1")
fib(n):
return n if n in [0, 1] else fib(n - 2) + fib(n - 1)

Comment and share

  • page 1 of 1

魏传柳(2824759538@qq.com)

author.bio


Tencent


ShenZhen,China