魑魅魍魉 发布的文章

Plone主题默认提供了几个Portlet是无法在管理页面中去除的,但是我们的Diazo主题中可以加一个 portlets.xml 配置文件,然后使用下面的代码即可去除之:

<?xml version="1.0"?>
<portlets>
<assignment remove="true" name="login" category="context" key="/" manager="plone.leftcolumn" type="portlets.Login" />
<assignment remove="true" name="navigation" category="context" key="/" manager="plone.leftcolumn" type="portlets.Navigation" />
<assignment remove="true" name="calendar" category="context" key="/" manager="plone.rightcolumn" type="portlets.Calendar" />
<assignment remove="true" name="events" category="context" key="/" manager="plone.rightcolumn" type="portlets.Events" />
<assignment remove="true" name="news" category="context" key="/" manager="plone.rightcolumn" type="portlets.News" />
<assignment remove="true" name="recent-items" category="context" key="/" manager="plone.rightcolumn" type="portlets.recent" />
<assignment remove="true" name="review" category="context" key="/" manager="plone.rightcolumn" type="portlets.review" />
</portlets>

我们除了使用完整的 Plone 安装 Plone 外,还可以使用更为高级的自定义安装方式安装,本文将介绍怎么从无到有使用 buildout 工具安装 Plone。

首先,我们需要创建一个文件夹,将来所有有关 Plone 的内容都将保存在这个目录中,即然是这样,那也就是说,其实你可以在一个系统中安装多个 Plone,要满足安装条件,我们还需要在系统中选安装 Python 2.6,我这里使用 virtualenv 工具创建一个虚拟机,在这个里面使用 Python 2.6

$ virtualenv —python=python2.6 /Users/pantao/VirtualEnv/plone
$ cd /Users/pantao/VirtualEnv/plone
$ source ./bin/activate

我们现在的工作目录就是 /Users/pantao/VirtualEnv/plone,我们将在这个目录下面来创建Plone Buildout。

接下来,下载 http:// svn.plone.org/svn/plone/buildouts/plone-coredev/branches/4.1/ bootstrap.pysvn.plone.org/svn/plone/buildouts/plone-coredev/branches/4.1/ bootstrap.py 这个引文件,这个文件是用来创建 Buildout 工具的。

wget http:// svn.plone.org/svn/plone/buildouts/plone-coredev/branches/4.1/ bootstrap.py

第三步就是在当前目录下创建一个名为 buildout.cfg 的文件,将下面的内容添加到该文件 中:

[buildout]
extends =
http://dist.plone.org/release/4.1/versions.cfg
parts = instance
[instance]
recipe = plone.recipe.zope2instance
user = admin:admin
eggs = Plone

现在我们可以初始化 buildout 了,在当前目录下面,运行下面这一行命令:

$ python bootstrap.py —distribute

这里,我直接使用 python 来运行它 bootstrap.py 是因为我们现处于 Virtual env 中,而这个 Vittual env 就是 Python 2.6 的,但是如果你是进行全局安装,那么有可能直接执行 python 命令运行的并不是 Python 2.6,这个需要你就需要在这里给出完整的 Python 2.6 的目录,比如我的本本上面, Python 2.6 的完整路径是下面这样的:

/System/Library/Frameworks/Python.framework/Versions/2.6/bin/python2.6

接着,我们执行下面命令来将 Plone buildout 出来,这个过程会从网络上下载很多文件,包括 Zope、Plone 本身以及其它一些需求包,根据网速,可能会需要不同的时间, Buildout 过程完成之后,就可以使用下面命令以 Debug 模式启动 Plone 了:

$ ./bin/instance fg

当脚本提示你已经 Ready for request 的时候,你就可以通过:“http://localhost:8080/”:http://localhost:8080/ 来访问你刚才创建的 Plone 站点。

Plone站点安装好了之后,我们总不能永远都使用Plone默认提供的功能和样式,总得根据自己的需要安装一些新的插件,修改修改主题或者安装新的主题,本文记录Plone新插件的安装方法与主题的简单自定义。

安装新的插件

最简单地安装Plone插件方法就是将想要安装的插件名称添加到 buildout.cfg 文件中,如下代码所示:

############################################
# Eggs
# ----
# Add an indented line to the eggs section for any Python
# eggs or packages you wish to include.
#
eggs =
Plone
Pillow
lxml
Products.PloneFormGen
webcouturier.dropdownmenu
collective.easyslider

在上面的代码中,我们除了Plone系统默认安装的插件外,还安装了三个新的插件:@Products.PloneFormGen@、@webcouturier.dropdownmenu@、@collective.easyslider@,这三个插件分别让我的Plone站点可以添加自定义表单,有下拉菜单,还可以很方便在页面中添加Slides。

将新的插件的名称添加到 buildout.cfg 文件中的 eggs 段之后,我们需要更新Plone,重新 buildout它。

首先我们需要进入 buildout.cfg 文件所在的目录(这是必须在,在其它目录中,是无法更新 Plone 的,会提示找不到 buildout.cfg 文件,进入该目录之后,运行下面的命令:

./bin/buildout
PanTao-Macbook-Pro:zinstance pantao$ ./bin/buildout
Uninstalling instance.
Installing instance.
Updating zopepy.
Updating zopeskel.
Updating backup.
Updating chown.
chown: Running echo Dummy references to force this to execute after referenced parts
echo /Applications/Plone/zinstance/var/backups
chmod 600 .installed.cfg
find /Applications/Plone/zinstance/var -type d -exec chmod 700 {} ;
chmod 744 /Applications/Plone/zinstance/bin/*
Dummy references to force this to execute after referenced parts
/Applications/Plone/zinstance/var/backups
Updating repozo.
Updating unifiedinstaller.
*************** PICKED VERSIONS ****************
[versions]
Products.PloneFormGen = 1.7.0
Products.PythonField = 1.1.3
Products.TALESField = 1.1.3
Products.TemplateFields = 1.2.5
collective.easyslider = 0.7.4
collective.js.jqueryui = 1.8.13.1
webcouturier.dropdownmenu = 2.3
*************** /PICKED VERSIONS ***************

之后我们需要停止当前正在运行的Plone Instance,然后再重新开启它:

PanTao-Macbook-Pro:zinstance pantao$ ./bin/plonectl stop
instance: .
daemon process stopped
PanTao-Macbook-Pro:zinstance pantao$ ./bin/plonectl start
instance: . .
daemon process started, pid=1417

重新启动之后,我们还需要进入站点的设置页面,找到附加组件,就可以看到我们刚刚安装的新组件了,这个时候我们就可以启动或者关闭组件。

自定义Plone站点样式

自定义Plone站点样式,我们可以开发新的主题,但是最简单的,莫过于修改Plone现有的主题。

修改 Plone 站点的 Logo

要修改 Plone 站点默认的 Plone Logo,我们可以以管理员身份登陆站点,然后进入 ZMI( 进入站点设置页面,找到 Zope 管理界面,即进入了 ZMI),在该页面中,进入 portal_skins -> portal_images,之后我们可以看到 logo.png文件,点击这个文件,之后在进入的页面,点击 customize,即可自定义该 Logo,上传我们自己的 logo,之后保存,再返回网站前端就可以看到新的 Logo 已经修改好了。

修改 Plone 站点的 CSS 样式表

Plone 并不建议修改它本身自带的样式表(几乎所有现代的CMS都不建议这么做),而是使用新的样式去覆盖它本身的样式,现在我们要修改 Plone 的样式表的话,首先需要开启 Plone 站点的开发者模式(Debug Mode),重新打开 buildout.cfg 文件,找到下面这段代码:

############################################
# Debug Mode
# ----------
# Change debug-mode to "on" to run in development mode.
# This will dramatically slow Plone.
#
debug-mode = off

将 debug-mode 打开,即修改成为下面这一行:

debug-mode = on

之后和安装新的组件一样,需要更新 Plone Instance,更新完了之后,进入ZMI,找到 portal_skins -> portal_styles,之后你可以看到 ploneCustom.css文件,我们只需要 将新的样式加入到这个文件之后,再保存即可。

上传文件的最简单的一个解决方案其实确实很简单,就下面这几个步骤:

  1. 一个被标记为 enctype="multipart/form-data" 且包含一个 <input type="file| /> 标签的表单(<form> )。
  2. 程序可以从 request.files 中直接访问已上传的文件。
  3. 通过 save() 函数将已上传的文件从临时文件存放位置移动到我们希望它存放的位置。

简单的开始

让我们从一个最简单的上传文件的应用来始,在这个应用中,用户上传的文件会被存放在一个特别的文件目录中,显示会显示该文件给用户,先让我们看看整个应用的引导程序:

import os
from flask import Flask, request, redirect, url_for
from werkzeug import secure_filename
UPLOAD_FOLDER = '/path/to/the/uploads'
ALLOWED_EXTENSIONS = set(['txt','pdf','png','jpg','jpeg','gif'])
app = Flask(__name__)
app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER

最开始我们需要导入一些应用中用到的模块或函数,上面导入的大多数模块和函数我们以前也都见过了,只是这个 werkzeug.secure_filename() 函数我将在稍后再详细的说明一下,在上面的代码段中, UPLOAD_FOLDER 指定了我们文件上传之后在服务器上的存放位置, ALLOWED_EXTENSIONS 则指定了允许上传的文件类型,但是你可以看到我并没有什么 一个URL规则来指定如何访问文件,因为在通常情况,使用服务器软件来服务这些软件比我们用程序发送效率更高,我们只需要在程序里面指定文件的URL路径即可。

至于为什么我们会限定上传的文件类型,这个原因很简单吧,你总不会想让你的用户可以存放任何文件到你的服务器上吧?如果你允许用户上传任何类型文件,那么这之间的风险将很大,比如他们可以上传一个可执行的脚本到你的服务器上,而如果这个脚本还正好处于一个可执行的目录中的话,那么肯定是很不安全的了。

接下面我们将来检查上传至服务器的文件的格式,如果文件允许被上传,那么我们会指定将用户页面转向至刚刚上传的文件上。

def allowed_file(filename):
return '.' in filename and filename.rsplit('.',1)[1] in ALLOWED_EXTENSIONS
@app.route('/', methods = ['GET','POST'])
def upload_file():
if request.method == 'POST':
file = request.files['file']
if file and allowed_file(file.filename):
filename = secure_filename(file.filename)
file.save(os.path.join(app.config['UPLOAD_FOLDER'], filename))
return redirect(url_for('uploaded_file', filename = filename))
return '''
<!DOCTYPE html>
<title>Upload New File</title>
<h1>Upload File</h1>
<form action="/" method="POST" enctype="multipart/form-data">
<input type="file" name="file" />
<input type="submit" value="Upload" />
</form>
'''

一些给专家们的建议

在上面的代码中, secure_filename() 到底做了什么呢?我们先从一个最觉提起的问题上来说:“永远不要相信用户的输入”,你就想象一下,如果某一个用户上传了有下面这样文件名的文件至你的服务器:

filename = "../../../../home/username/.bashrc"

然后再想象一下,如果 ../ 的个数正好,那么,将这个文件名的文件与你的上传路径合并之后,他将有可能修改你的服务器的文件,当然,这种情况很少能出现,而且一般的用户也很少会恶意的去这么做,但是相信我,专门这样做的黑客如果想要黑你,那么他一定会想尽办法这么去做,所以,还是那句话:“永远不要相信任何用户的输入”。

我们从Python命令行中来看一下下这个函数是如何工作的:

>>> secure_filename('../../../../home/username/.hashrc')
'home_username_.bashrc'

现在只剩下最后一件事情了,将用户上传的文件发送给用户查看,在 Flask 0.5 版本中,我们可以使用下面这样函数来做这件事情:

from flask import send_from_directory
@app.route('/uploads/<filename>')
def uploaded_file(filename):
return send_from_directory(app.config['UPLOAD_FOLDER'], filename)

另外,你还可以将 uploaded_file 注册成为 build_only ,同时使用共享数据中间介(SharedDataMiddleware),在老版本的Flask也同样适用:

from werkzeug import ShareDataMiddleware
app.add_url_rule('/uploads/<filename>', 'uploaded_file', build_only = True)
app.wsgi_app = ShareDataMiddleware(app.wsgi_app, {'uploads': app.config['UPLOAD_FOLDER'] })

现在你运行你的程序,应该是可以实现文件上传,检测文件类型以及文件名,输出文件给用户查看的所有功能了。

更加高级的文件上传

那么Flask到底是怎么样处理文件上传的呢?是这样的,当用户上传文件至服务器时,如果文件小的话(这个在不同系统上面是不一样的量),Flask会将其保存在内存中,或者如果文件很大的话,会被保存到一个临时文件中(可以使用 tempfile.gettempdir()获取到),默认情况下,只要服务器内存允许,Flask不会管用户上传的文件有多大,但是我们很多时候并不希望这样,这时,我们可以使用通过设置 app 的 MAX_CONTENT_LENGTH 参数来限定用户上传的文件最大大小:

from flask import Flask, Request
app = Flask(__name__)
app.config['MAX_CONTENT_LENGTH'] = 16 * 1024 * 1024

上面的设置将限定用户最大只允许上传不超过 16Mb的文件,如果用户上传的文件过大,刚Flask会抛出一个 RequestEntityTooLarge 异常。

最近的一个Drupal项目中,要用到Drupal 7 的Image Style(也没有哪个项目不用这个东西吧?),以前一直都是使用 Apache服务器,还没出现什么问题,原因可能是因为Drupal默认就对Apache有更好的支持吧,在 .htaccess 文件中可能已经提供了规则,但是在Nginx中,因为并不是原生的Rewrite Rule,所以还是会出现问题的。

Drupal 7 中的Image Styles可以让我们上传一张图片,在不同的地方得到各种各样不同大小和效果的图片,它可以对图片进行去色,翻转,改变大小,裁切等各种基本操作,但是前提是,我们需要通过 Drupal 去访问这些图片,而不是直接访问图片在服务器上的URL地址,比如:http://www.lorecore.net/media/default/images/2011-11/1.jpg 这样图片,如果我想在想要调用它的 large 样式的复本,那么通过 Image Style的地址应该是下面这样的:

http://www.lorecore.net/media/default/images/styles/large/public/images/2011-11/1.jpg

这样访问在Apache中应该是没有问题,但是在Nginx中,可能根本就访问不到,我也是整了一整天,最后发现一个问题,在我的MAMP 里面,在处理这种图片的时候,PHP会产生Error,但是在我的VPS上面,却没有这个Error,当我把MAMP中的问题处理完了之后,Image Style可以正常工作了,但是在VPS上面,因为没有产生Error,那么很有可能就是:访问Image Style处理之后的图片,必须经过 Drupal ,而不能直接从服务器上下载。

这是因为Drupal的Image Style访问的图片会将起重写到 /?q=/media/default…. 这样的地址,而并不是直接让Apache来返回的,而Nginx因为没有相关的Rewrite Rule,所以并没有将静态文件的访问转到 Drupal 上,要修复 Drupal 在Nginx上面的这个问题,我们只需要让访问Image Style的Cache目录的图片的时候,都将其Rewrite到Drupal上即可,在Nginx的配置文件中加入如下代码:

location ^~ /media/default/styles/ {
index index.php index.html;
if (!-e $request_filename) {
rewrite ^/(.*)$ /index.php?q=$1 last;
break;
}
}

上面的代码,可以让对 /media/default/styles/ 这个目录下所有文件的请求都Rewrite到 /?q=/media/default/styles/.. 上,在你的项目中,需要将 “/media/default/styles/” 修改成为你自己的 Styles Image Cache 目录,比如如果你使用的是Drupal默认的文件路径,那么应该是 “/sites/default/files/styles/”。

使用 Python 和 Tkinter 实现的一个简单的计算器:

代码

#!/usr/bin/env python
# encoding: utf-8
"""
File: simple_calculator.py
Created by Pan Tao on 2011-09-24.
Copyright (c) 2011 CosTony.Com. All rights reserved.
"""
from Tkinter import *
def frame(root, side):
w = Frame(root)
w.pack(side = side, expand = YES, fill = BOTH)
return w
def button(root, side, text, command = None):
w = Button(root, text = text, command = command)
w.pack(side = side, expand = YES, fill = BOTH)
return w
class Calculator(Frame):
def __init__(self):
Frame.__init__(self)
self.pack(expand = YES, fill = BOTH)
self.master.title('Simple Calculator')
self.master.iconname('calculator')
display = StringVar()
Entry(self, relief = SUNKEN, textvariable = display).pack(side = TOP, expand = YES, fill = BOTH)
for key in ('123', '456', '789', '-0.'):
keyF = frame(self, TOP)
for char in key:
button(keyF, LEFT, char, lambda w=display, s = ' {} '.format(char): w.set(w.get() + s))
opsF = frame(self, TOP)
for char in '+-*/=':
if char is '=':
btn = button(opsF, LEFT, char)
btn.bind('<ButtonRelease-1>', lambda e,s = self, w = display: s.calc(w), '+')
else:
btn = button(opsF, LEFT, char, lambda w = display, c = char: w.set(w.get() + ' ' + c + ' '))
clearF = frame(self, BOTTOM)
button(clearF, LEFT, 'Clear', lambda w = display: w.set(''))
def calc(self, display):
try:
display.set(`eval(display.get())`)
except ValueError:
display.set('ERROR')
if __name__ == '__main__':
Calculator().mainloop()

详细的步骤

  1. 整个程序从两个函数开始, frame() 与 button() ,这两个函数可以让我们更回方便的创建小工具,而且是整个程序更加紧凑,我使用的是 pack 布局管理器(在程序开发中,经常会将一些最常用的函数或方法写为公共函数,这样可以使得程序更加紧凑,可读性也更好,更易维护)。
  2. 我使用 Frame 构造器构造了最顶级的封闭框架,接着设置了它的名称与图标。
  3. 接着,创建了该计算器最上部的显示条(当我们使用该计算器时,输入与结果都会在这里面显示),同时还定义了一个Tkinter变量,它可以访问Tkinter小工具的内容。
  4. 在 Python 中,任何一个字符串都是一个元素为字符的序列,所以,它们都是可以被迭代的。
  5. 我使用了 button() 函数来创建了多个按钮,并且将其添加到它们的上一级框架中。
  6. 我们为

什么是字母算术?或者可以被称为 Cryptarthms 或者 Alphametics,如下面这样的就是:

HAWAII + IDAHO + IOWA + OHIO == STATES
510199 + 98153 + 9301 + 3593 == 621246

其字母与数字的对应关系为:

H = 5
A = 1
W = 0
I = 9
D = 8
O = 3
S = 6
T = 2
E = 4

solve() 函数

import re
import itertools
def solve(puzzle):
words = re.findall('[A-Z]+', puzzle.upper())
unique_characters = set(''.join(words))
assert len(unique_characters) <= 10, 'Too many letters'
first_letters = {word[0] for word in words}
n = len(first_letters)
sorted_characters = ''.join(first_letters) + 
''.join(unique_characters - first_letters)
characters = tuple(ord(c) for c in sorted_characters)
digits = tuple(ord(c) for c in '0123456789')
zero = digits[0]
for guess in itertools.permutations(digits, len(characters)):
if zero not in guess[:n]:
equation = puzzle.translate(dict(zip(characters, guess)))
if eval(equation):
return equation
if __name__ == '__main__':
import sys
for puzzle in sys.argv[1:]:
print(puzzle)
solution = solve(puzzle)
if solution:
print(solution)

solve() 函数详解

words = re.findall('[A-Z]+', puzzle.upper())
unique_characters = set(''.join(words))

获取到所有字母,再将其保存到一个 SET 中,这个是该程序要做的第一件事情,在这里我们就使用re 模块的 findall() 函数,它接受一个正则表达式和一个字符串作为参数,并且取出所有该字符串中出现该模式的地方并保存到一个列表中。

对于取出之后的列表,我们还不能接着下一步工作,因为我们需要知道每一个字母对该的数字是什么,而不是一个字符串对应什么数字,所以我们需要将所有字符串中出现的字母取出来,这个时候可以用到 set ,它可以去除重复的值,还使用了字符串的 join() 函数,它将多个字符串使用一个空字符连接到一起,由于在Python中,字符串就是一个字符的序列,所以它可以直接被用来作为 set() 的参数,我们最后得到的将是一个以字母为元素的 Set 实例。

>>> words = re.findall('[A-Z]+', 'SEND + MOre = Money'.upper())
>>> words
['SEND', 'MORE', 'MONEY']
>>> set(words)
{'MONEY', 'SEND', 'MORE'}
>>> unique_charactors = set(''.join(words))
>>> unique_charactors
{'E', 'D', 'M', 'O', 'N', 'S', 'R', 'Y'}

接下来我们需要做的是判断字母数量是否大于 10 个,这个原因是:每一个不同的字母都必须对应一个不同的数字,然后总共只有 0 – 9 这 10 个数字,所以,如果字母数大于 10 个的话,那肯定会有多余的字母没有数字与之对应,也就不可能形成字母算术了。

这里使用了一个 assert 语句,该语句后面可以跟任何全法的 Python 表达式,如果表达式的结果为 True,那么该 assert 语句就与普通的表达式一样,但是如果运行结果为 False 的话,该语句将会抛出AssertionError 异常,assert 语句后面还可以再添加一个异常说明的字符串,当抛出异常时,该字符串将一并返回:

>>> assert len(unique_charactors) <= 10
>>> assert len(unique_charactors) >= 10, 'Aha, AssertionError has been catched'
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AssertionError: Aha, AssertionError has been catched

上面产生错误的代码其实与下面这个代码是待价的:

if len(unique_charactors) < 10:
raise AssertionError('Aha, AssertionError has been catched')

再接下来,就是很有趣的东西了,这是一个排列组合的问题,先获得总计有多少个字母,然后将就可以知道,对多有多少种可能的结果了,因为每一个字母对应一个数字,所以,我们可以把问题科化为0-9十个数字有多少种 N 个值的排列方法,这个N 就是字母的个数,在这里我们使用了一个十分有趣的函数: itertools.permutations()

itertools.permutaions() 函数

先来看一段代码:

>>> for i in itertools.permutations('ABC',2): print(i)
...
('A', 'B')
('A', 'C')
('B', 'A')
('B', 'C')
('C', 'A')
('C', 'B')
>>> for i in itertools.permutations('ABC',3): print(i)
...
('A', 'B', 'C')
('A', 'C', 'B')
('B', 'A', 'C')
('B', 'C', 'A')
('C', 'A', 'B')
('C', 'B', 'A')
>>> for i in itertools.permutations('ABC',4): print(i)
...

从上面的代码中可以看到,该函数接受一个序列(可以是任何迭代器或者列表等数据)以及一个排序的元素数目的参数,比如上例中第一段代码,我们就是将’A’,‘B’,‘C’三个字母进行排列,排列仅仅只有两个元素,而且每一个元素不能相同,permutations 会将鳘次排列返回,这个要让我们自己去写排列的话,还真的不简单,不过 Python 已经帮我们把这些都得很好了。

在本文的代码中,将每一个排列进行测试,如果得到正确的结果,刚停止运行并返回结果。

把所有东西放在一起

总的来说: 这个程序通过暴力解决字母算术谜题, 也就是通过穷举所有可能的解法。为了达到目的,它

  1. 通过re.findall()函数找到谜题中的所有字母
  2. 使用集合和set()函数找到谜题出现的所有不同的字母
  3. 通过assert语句检查是否有超过10个的不同的字母 (意味着谜题无解)
  4. 通过一个生成器对象将字符转换成对应的ASCII码值
  5. 使用itertools.permutations()函数计算所有可能的解法
  6. 使用translate()字符串方法将所有可能的解转换成Python表达式
  7. 使用eval()函数通过求值Python 表达式来检验解法
  8. 返回第一个求值结果为True的解法

最近遇到的一个问题是这样的:应用有一个 config.py 文件,在这个文件里面定义了下面这个参数:

#: Installed modules
INSTALLED_MODULES = [
{'name': 'main', 'prefix': ''},
]

这是我的一个 Flask 项目中使用到的,在我的项目中,有很多个模块,比如 user , group 等,但是我并不是在任何一个站点上都需要所有的这些模块,所以我就在配置文件里面加入了上面的配置,只有这里面的模块才被安装,并且,这里面没有的模块我就不再导入进来,所以就需要动态的导入模块了,这个时候我以前使用的 import 或者 from package import module 等都不能工作。

要解决这个问题,有两个办法,一个是使用 exec 声明,如下面这样的:

>>> sys
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
NameError: name 'sys' is not defined
>>> s = 'import sys'
>>> exec s
>>> sys
<module 'sys' (built-in)>

这个办法可以解决,但是还有另一个更好的办法就是使用 __import__() 函数:

该函数与 import 声明很相似,但是它是一个实实在在的函数,我们可以对导入过程更加细致的控制,比如我最上面所说的需求可以像下面这样实现:

for m in INSTALLED_MODULES:
mod = __import__(m['name'])
app.register_blueprint('mod',prefix = m['prefix'])

另外,如果我们有另一个需求,需要导入一个指定的列表内的所有的模块,还可以使用一个叫做map() 的函数:

>>> module_names = ['sys','os','re','unittest']
>>> module_names
['sys', 'os', 're', 'unittest']
>>> modules = map(__import__, module_names)
>>> modules
[<module 'sys' (built-in)>, <module 'os' from '/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/os.pyc'>, <module 're' from '/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/re.pyc'>, <module 'unittest' from '/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/unittest/__init__.pyc'>]
>>> modules[0].version
'2.7.1 (r271:86832, Jun 16 2011, 16:59:05) n[GCC 4.2.1 (Based on Apple Inc. build 5658) (LLVM build 2335.15.00)]'
>>> import sys
>>> sys.version
'2.7.1 (r271:86832, Jun 16 2011, 16:59:05) n[GCC 4.2.1 (Based on Apple Inc. build 5658) (LLVM build 2335.15.00)]'

在 Flask 应用开发中,使用最多的一个方法可能就是 route() 了,它可以将一个URL地址与一个 view 函数绑定,当用户访问 URL 时,就会调用与其绑定了的 view 函数,而其 view 函数的处理结果将返回给浏览器,我们最常使用的方法如下面这样的:

from flask import Flask
app = Flask(__name__)
@app.route('/')
def index():
return 'Index Page'
if __name__ == '__main__':
app.run()

运行上面的代码,我们将可以使用访问 http://127.0.0.1:5000 ,页面返回“Index Page”,这整个过程是下面这样的:

Flask应用 app 有 route() 这个方法,代码如下:

 def route(self, rule, **options):
def decorator(f):
self.add_url_rule(rule, None, f, **options)
return f
return decorator

当我们启动 app 之后,app 首先会调用该方法,它会将其下一行所定义的函数作为参数传递给route() 方法,@route()@ 方法会运行 add_url_rule() 方法,将该函数绑定到一个 URL 规则上,接着返回该函数,整个过程是一个“函数注册”的过程,注册方法为 add_url_rule() 函数,接着我们再来看一下下 add_url_rule() 方法:

 def add_url_rule(self, rule, endpoint=None, view_func=None, **options):
if endpoint is None:
endpoint = _endpoint_from_view_func(view_func)
options['endpoint'] = endpoint
methods = options.pop('methods', None)
if methods is None:
methods = getattr(view_func, 'methods', None) or ('GET',)
provide_automatic_options = False
if 'OPTIONS' not in methods:
methods = tuple(methods) + ('OPTIONS',)
provide_automatic_options = True
options['defaults'] = options.get('defaults') or None
rule = self.url_rule_class(rule, methods=methods, **options)
rule.provide_automatic_options = provide_automatic_options
self.url_map.add(rule)
if view_func is not None:
self.view_functions[endpoint] = view_func

add_url_rule() 方法的运行流程如下:

  1. 检查 endpoint 参数是否为 None,如果为 None,则指定默认的 endpoint,这个我们一般都没有使用,所以就像我以前也都不知道有这么个参数
  2. 将 endpoint 加入到 options 参数中
  3. 如果未指定 methods ,则程序尝试去自动获取 methods 列表(这个 methods 是浏览器向服务器请求的方法列表,有 POST,GET,PUT 等)或者默认使用 GET 方法
  4. 如果 ‘OPTIONS’ 这项不在 methods 列表中,则将其加入
    获取“defaults”参数列表,这个是一个字典数据,比如 “defaults = {‘id’:1,‘title’=‘test’}”
  5. 根据参数创建 rule 对象。并将其添加到 app 的URL映射表中

通过上面的代码我们可以大概的知道一下下最开始那个添加应用路由的过程,那么我们其实也可以不使用默认的方法,而使用下面这样的方法来添加URL映射:

def index():
return 'Index Page'
app.add_url_rule('/',None,index)

这种方法可以让我们动态的添加URL映射:

def aha():
return 'Aha, Insight!'
@app.route('/')
def index():
app.add_url_rule('/aha', None, aha)
return 'Index Page'

运行上面的应用,访问 http://127.0.0.1:5000/aha 我们将得到错误页面:

Not Found
The requested URL was not found on the server.
If you entered the URL manually please check your spelling and try again.

因为这个URL还没有映射到我们的 app 中,也就不可能有相关的回调函数返回任何内容了,我们再运行访问一次“ http://127.0.0.1:5000 ”再返回去 http://127.0.0.1:5000/aha ,将得到“Aha, Insight!”的结果,这是因为当我们访问 http://127.0.0.1:5000/ 时,@index()@ 动态的添加了一个到 aha() 函数的映射。

Flask 的 url 映射都保存在一份叫作 url_map 的 werkzeug.routing.Map 对象中.

Python 编写 Server 端的步骤

第一步:创建 socket 对象

调用 socket 构造函数,如:

socket = socket.socket( family, type )
  • family 参数代表地址家族,可以为 AF_INET 或者 AF_UNIX ,AF_INET 家族包括 Internet 地址,AF_UNIX 家族则用于同一台机器上的进程间通信
  • type 参数代表套接字类型,可以为 SOCK_STREAM (流套接字) 或者 SOCK_DGRAM (数据报套接字)。

第二步:将 socket 绑定到指定地址

这是通过 socket 对象的 bind 方法来实现的:

socket.bind( address )

由 AF_INET 创建的套接字, address 必须是一个双元素无组,格式是 ( host, port ),host 代表主机, port 代表端口号,如果端与正在使用,主机名不正确或者商品端口已被保留, bind 方法将引发 socket.error 异常。

第三步:使用 listen 套接字的 listen 方法接收连接请求

socket.listen( backlog )

backlog 指定最多允许多少个客户连接到服务器,它的值至少为 1,收到连接请求后,这些请求需要排队,如果队列已满,就拒绝请求。

第四步:服务器套接字通过 socket 的 accept() 方法等待客户请求一个连接

connection, address = socket.accept()

调用 accept 方法时, socket 会进入 “waiting” 状态,客户请求连接时,方法建立连接并返回服务器, accept 方法返回一个含有两个元素的元组(connection, address),第一个元素 connection 是新的 socket 对象,服务器必须通过它与客户通信;第二个元素 address 是客户的 Internet 地址。

第五步:处理

服务器和客户端通过 send 和 recv 方法通信(传输数据)。服务器调用 send ,并采用字符串形式向客户发送信息,send 方法返回已发送的字符个数,服务器使用 recv 方法从客户端接收信息,调用 recv时,服务器必须指定一个整数,它对应于可通过本次方法调用来接收的最大数据量。 recv 方法在会进入“blocked”状态,最后返回一个字符串,用它表示接收到的数据,如果发送的数据量超过了 recv 所允许的数据会被截取,多余的数据将缓冲于接收端,以后调用 recv 时,多余的数据会从缓冲区删除(以及上次调用 recv 以来,客户可能发送的其它任何数据)。

第六步:传输结束

服务器调用 socket 的 close 方法关闭连接

Python 编写客户端的步骤

第一步:创建一个 socket 以连接服务器

socket = socket.socket( family, type)

第二步:使用 socket 的 connect 方法连接服务器

对于 AF_INET 家族,连接格式如下:

socket.connect(( host, port ))

host 代表服务器的主机名或者 IP, port 代表服务器进程所绑定的端口号,如果连接成功,客户端就可以与服务器进行通信了,如果连接失败,会引发 socket.error异常

第三步:处理阶段

客户和服务器将通过 send 方法和 recv 方法通信

第四步:传输结果

客房端通过 socket 的 close 方法关闭连接

整个示例的完整代码

server.py 文件

#!/usr/bin/env python
# encoding: utf-8
"""
File: server.py
Created by Pan Tao on 2011-09-20.
Copyright (c) 2011 CosTony.Com. All rights reserved.
"""
def main():
import socket
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.bind(('localhost', 8000))
sock.listen(5)
while True:
connection, address = sock.accept()
try:
connection.settimeout(5)
buf = connection.recv(1024)
if buf == '1':
print 'Client {} connected to server!'.format(address)
connection.send('Welcome to server!')
else:
connection.send('Please go out!')
except socket.timeout:
print 'Time out!'
connection.close()
if __name__ == '__main__':
main()

client.py 文件

#!/usr/bin/env python
# encoding: utf-8
"""
File: client.py
Created by Pan Tao on 2011-09-20.
Copyright (c) 2011 CosTony.Com. All rights reserved.
"""
def main():
import socket
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.connect(('localhost', 8000))
import time
time.sleep(2)
sock.send('1')
print sock.recv(1024)
sock.close()
if __name__ == '__main__':
main()

测试运行

我们先运行 server.py 以开启服务器

python server.py

然后运行 client.py 连接服务器

python client.py

运行结果:

Server 端将打印出:

Client ('127.0.0.1', 56933) connected to server!

客户端将打印出:

Welcome to server!

该脚本实在是简陋哈,只能上传文件和下载文件,显示当前服务器上的文件列表,没有其它的功能,只是自己学习 Bottle 的练习了:

#!/usr/bin/env python
# encoding: utf-8
"""
File: com.costony.fileuploader.py
Created by Pan Tao on 2011-09-20.
Copyright (c) 2011 CosTony.Com. All rights reserved.
"""
from bottle import route, template, run, request, redirect, static_file
import os
index_tpl = '''
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
    <title>COS TONY FILE UPLOADER</title>
    <style type="text/css">
    body {font-family: arial; font-size: 80%;line-height:2em;}
    a {color: #393; font-size:1.5em; text-decoration:none}
    h1 { font-size:2em; padding-bottom: .3em;border-bottom: 1px solid #999;}
    </style>
</head>
<body>
<h1>Upload File</h2>
<form action = "/upload" method = "POST" enctype="multipart/form-data">
<input type="file" name="data" />
<input type="submit" value="Upload" />
</form>
<h1>Files List</h1>
<ul>
%for file in files:
<li><a href="/download/{{file}}">{{file}}</a></li>
%end
</ul>
</body>
</html>
'''
@route('/')
def index():
return template(index_tpl, files = [f for f in os.listdir('./files') if not f.startswith('.')])
@route('/upload', method = 'POST')
def upload():
data = request.files.get('data')
save_file = open('./files/{}'.format(data.filename), 'wb')
if data.file:
try:
buf = data.file.read(data.bufsize)
while buf:
save_file.write(buf)
buf = data.file.read(data.bufsize)
save_file.close()
return redirect('/')
except Exception, e:
return redirect('/')
@route('/download/:filename')
def download(filename):
return static_file(filename, root='./files')
if __name__ == '__main__':
    run(reloader = True)

Bottle 是一个非常小巧但高效的微型 Python Web 框架,它被设计为仅仅只有一个文件的Python模块,并且除Python标准库外,它不依赖于任何第三方模块。

  • 路由(Routing):将请求映射到函数,可以创建十分优雅的 URL
  • 模板(Templates):Pythonic 并且快速的 Python 内置模板引擎,同时还支持 mako, jinja2, cheetah 等第三方模板引擎
  • 工具集(Utilites):快速的读取 form 数据,上传文件,访问 cookies,headers 或者其它 HTTP相关的 metadata
  • 服务器(Server):内置HTTP开发服务器,并且支持 paste, fapws3, bjoern, Google App Engine, Cherrypy 或者其它任何 WSGI HTTP 服务器

安装 Bottle

正如上面所说的, Bottle 被设计为仅仅只有一个文件,我们甚至可以不安装它,直接将 bottle.py 文件下载并复制到我们的应用中就可以使用了,这是一个好办法,但是如果还是想将其安装,那么我们可以像安装其它的 Python 模块一样:

sudo easy_install -U bottle

如果我们直接将 bottle.py 下载到自己的应用中的话,我们可以建立下面这样的目录结构:

+ application
+----bottle.py
+----app.py

我们可以将下面的创建 Bottle 实例的示例代码复制到 app.py 文件中,运行该文件即可。

示例:Bottle 的 “Hello World” 程序

下面的代码我们创建了一个十分简单但是完整的 Bottle 应用程序(在Python Consle)中:

>>> from bottle import route, run
>>> @route('/hello/:name')
... def index(name = 'World'):
... return '<strong>Hello {}!'.format(name)
...
>>> run(host='localhost',port=8080)
Bottle server starting up (using WSGIRefServer())...
Listening on http://localhost:8080/
Use Ctrl-C to quit.

在 Python Consle中输入上面的代码,我们就得到了一个最简单但完整的 Web 应用,访问:“http://localhost:8080/hello/bottle”试试。上面到底发生了什么?

  1. 首先,我们导入了两个 Bottle 的组件, route() Decorator 和 run() 函数
  2. route() 可以将一个函数与一个URL进行绑定,在上面的示例中,route 将 “/hello/:name’ 这个URL地址绑定到了 “index(name = ‘World’)” 这个函数上
  3. 这个是一个关联到 “/hello” 的 handler function 或者 callback ,任何对 “/hello” 这个URL的请求都将被递交到这个函数中
  4. 我们获得请求后,index() 函数返回简单的字符串
  5. 最后,run() 函数启动服务器,并且我们设置它在 “localhost” 和 8080 端口上运行

上面这种方法仅仅只是展示一下下 Bottle 的简单,我们还可以像下面这样,创建一个 Bottle 对象 app,然后我们会将所有的函数都映射到 app 的 URL 地址上,如上示例我们可以用下面这种办法来实现:

from bottle import Bottle, run
app = Bottle()
@app.route('/hello')
def hello():
return "Hello World!"
run(app, host='localhost', port=8080)

Bottle 的这种 URL 地址映射方法与我一直使用的 Flask 的地址映射方法很相似,到现在为止,我似乎只看到它们只是语法上面有些话不同。

路由器(Request Routing)

Bottle 应用会有一个 URL 路由器,它将 URL 请求地址绑定到回调函数上,每请求一些 URL,其对应的 回调函数就会运行一些,而回调函数返回值将被发送到浏览器,你可以在你的应用中通过route() 函数添加不限数目的路由器。

from bottle import route
@route('/')
@route('/index.html')
def index():
return '<a href="/hello">Go to Hello World Page</a>'
@route('/hello')
def hello():
return 'Hello World'

就像你看到,你所发出的访问请求(URL),应用并没有返回服务器上真实的文件,而是返回与该URL绑定的函数的返回值,如果其一个URL没有被绑定到任何回调函数上,那么 Bottle 将返回“404 Page Not Found” 的错误页面

动态路由(Dynamic Routes)

Bottle 有自己特有的 URL 语法,这让我们可以很轻松的在 URL 地址中加入通配符,这样,一个 route 将可以映射到无数的 URL 上,这些动态的 路由常常被用来创建一些有规律性的内容页面的地址,比如博客文章地址“/archive/1234.html” 或者 “/wiki/Page_Title”,这在上面的示例我已经演示过了,还记得吗?

@route(’/hello/:name’)
def hello(name = ‘World’): return ‘Hello {}!’.format(name)

上面的路由器,可以让我们通过“/hello/costony”或者“/hello/huwenxiao”等地址来访问,而 Bottle 返回的内容将是“Hello costony!” 或者 “Hello huwenxiao!”,“/hello/”之后的字符串交被返回来,默认的通配符将匹配所有下一个“/”出现之前的字符。我们还可以对通配符进行格式化:

@route('/object/:id#[0-9]+#')
def view_object(id):
return 'Object ID: {}'.format(id)

上面的路由将只允许 id 为由数字“0-9”组成的数字,而其它的字符串都将返回 404 错误。

HTTP 请求方法(Request Methods)

HTTP 协议为不同的需求定义了许多不同的请求方法,在 Bottle 中,GET方法将是所有未指明请求访问的路由会默认使用的方法,这些未指明方法的路由都将只接收 GET 请求,要处理如 POST, PUT 或者 DELETE 等等的其它请求,你必须主动地在 route() 函数中添加 method 关键字,或者使用下面这些 decorators:@get()@ , post() , put() , delete() 。

POST 方法在经常被用来处理 HTML 的表单数据,下面的示例演示了一个 登陆表单的处理过程:

from bottle import get, post, request
#@route('/login')
@get('/login')
def login_form():
return '''<form method = "POST">
<input name="name" type="text" />
<input name="password" type="password" />
<input type="submit" value="Login" />
</form>'''
#@route('/login', method = 'POST')
@post('/login')
def login():
name = request.forms.get('name')
password = request.forms.get('password')
if check_login(name, password):
return '<p>Your login was correct</p>'
else:
return '<p>Login failed</p>'

在上面的示例中,@/login@ 被绑定到两个不同的回调函数上,一个处理 GET 请求,另一个处理POST 请求,第一个返我们的登陆表单,第二个接收登陆表单提交的数据,并进行处理,得到结果后,返回结果。

自动回退(Automatic Fallbacks)

特殊的 HEAD 方法,经常被用来处理一些仅仅只需要返回请求元信息而不需要返回整个请求结果的事务,这些HEAD方法十分有用,可以让我们仅仅只获得我们需要的数据,而不必要返回整个文档,Bottle 可以帮助我们很简单的实现这些功能,它会将这些请求映射到与URL绑定的回调函数中,然后自动截取请求需要的数据,这样一来,你不再需要定义任何特殊的 HEAD 路由了。

静态文件路由(Routing Static Files)

对于静态文件, Bottle 内置的服务器并不会自动的进行处理,这需要你自己定义一个路由,告诉服务器在哪些文件是需要服务的,并且在哪里可以找到它们,我们可以写如下面这样的一个路由器:

from bottle import static_file
@route('/static/:filename')
def server_static(filename):
return static_file(filename, root='/path/to/your/static/files')

static_file() 函数是一个安全且方便的用来返回静态文件请求的函数,上面的示例中,我们只返回”/path/to/your/static/files” 路径下的文件,因为 :filename 通配符并不接受任何 “/” 的字符,如果我们想要“/path/to/your/static/files” 目录的子目录下的文件也被处理,那么我们可以使用一个格式化的通配符:

@route('/static/:path#.+#')
def server_static(path):
return static_file(path, root='/path/to/your/static/files')

错误页面(Error Pages)

如果任何请求的URL没有的到匹配的回调函数,那么 Bottle 都会返回错误页面,你可以使用 error()decorator 来抓取 HTTP 状态,并设置自己的相关回调函数,比如下面我们的处理404错误的函数:

@error(404)
def error404(error):
return '404 error, nothing here, sorry!'

这个时候,404 文件未找到错误将被上面的自定义404错误处理方法代替,传送给错误处理函数的唯一的一个参数是一个 HTTPError 实例,它非常将普通的 request,所以,你也可以有 request 中读取到,也可以写入 response 中,并且返回任何 HTTPError 支持的数据类型。

生成内容(Generating Content)

在纯粹的 WSGI中,你的应用能返回的数据类型是十分有限的,你必须返回可迭代的字符串,你能返回字符串是因为字符串是可以迭代的,但是这导致服务器将你的内容按一字符一字符的传送,这个时候,Unicode 字符将不允许被返回了,这是肯定不行的。

Bottle 则支持了更多的数据类型,它甚至添加了一个 Content-Length 头信息,并且自动编码 Unicode 数据,下面列举了 Bottle 应用中,你可以返回的数据类型,并且简单的介绍了一下这些数据类型的数据都是怎么被 Bottle 处理的:

数据类型 介绍
字典(Dictionaries) Python 内置的字典类型数据将自动被转换为 JSON 字符串,并且添加 Content-Type 为 ’application/json’ 的头信息返回至浏览器,这让我们可以很方便的建立基于 JSON 的API
空字符串,False,None或者任何非真的数据 Bottle 将为这类数据创建 ContentLength 头文件,被设置为 0 返回至浏览器
Unicode 字符串 Unicode 字符串将自动的按 Content-Type 头文件中定义的编码格式进行编码(默认为UTF8),接着按普通的字符串进行处理
字节串(Byte strings) Bottle 返回整个字符串(而不是按字节一个一个返回),同时增加 Content-Length 头文件标示字节串长度
HTTPError 与HTTPResponse 实例 返回这些实例就像抛出异常一样,对于 HTTPError,错误将被与相关函数处理
文件对象 然后具有 .read() 方法的对象都被看作文件或者类似文件的对象进行处理,并传送给 WSGI 服务器框架定义 wsgi.file_wrapper 回调函数,某一些WSGI服务器会使用系统优化的请求方式(Sendfile)来发送文件。
迭代器与生成品 你可以在你的回调函数使用 yield 或者 返回一个迭代器,只要yield的对象是字符串,Unicode 字符串,HTTPError 或者 HTTPResponse 对象就行,但是不允许使用嵌套的迭代器,需要注意的是,当 yield 的值第一次为非空是, HTTP 的状态 和 头文件将被发送到 浏览器

如果你返回一个 str 类子类的实例,并且带有 read() 方法,那它还是将按 字符串进行处理,因为字符串有更高一级的优先处理权。

改变默认编码

Bottle 依照 Content-Type 头文件中 charset 参数来对字符串进行编码,该头文件默认为 text/html; charset=UTF8 ,并且可以被 Response.content_type 属性修改,或者直接被 Response.charset 属性修改:

from bottle import response
@route('/iso')
def get_iso():
response.charset = 'ISO-8859-15'
return u'This will be sent with ISO-8859-15 encoding.'
@route('/latin9')
def get_latin():
response.content_type = 'text/html; charset=latin9'
return u'ISO-8859-15 is also known as latin9.'

由于某些罕见的原因,Python 编码的名称可能与 HTTP 编码的名称不一致,这时你需要做两方法的工作首先设置 Response.content_type 头文件,然后还需要设置 Response.charset 。

静态文件

你可以直接返回文件,但是 Bottle 推荐使用 static_file() 方法,它会自动的猜测文件的 mime-type,追加 Last-Modified 头文件,完全的自定义需要服务的文件路径,并且能处理错误(比如 404),并且它还支持 If-Modified-Since 头文件并且可以返回 304 Not Modified 响应,你还可以使用一个自定义的 mime-type 来重写 mime-type 猜测的值。

from bottle import static_file
@route('/images/:filename#.*.png#')
def send_image(filename):
return static_file(filename, root='/path/to/image/files', mimetype = 'image/png')
@route('/static/:filename')
def send_static(filename):
return static_file(filename, root='/path/to/static/files')

如果你真的需要,你还可以以异常的形式抛出文件。

强制下载

绝大多数浏览器在知道下载的文件的MIME类型并且该文件类型被绑定到某一个应用程序时(比如PDF文件),它们都会自动的打开该文件,如果你不想这样,你可以强制的要求浏览器进行下载。

@route('/download/:filename')
def download(filename):
return static_file(filename, root='/path/to/static/files', download=filename)

HTTP 错误与重定向

abort() 函数是创建 HTTP 错误页面的快捷方式:

from bottle import route, abort
@route('/restricted')
def restricted():
abort(401, 'Sorry, access denied.')

要将浏览器请求的地址重定向其它的地址,你可以向浏览器发送一个 303 see other 响应,redirect() 可以实现这个功能:

from bottle import redirect
@route('/wrong/url')
def wrong():
redirect('/right/url')

除了 HTTPResponse 或者 HTTPError 异常外,还会有 500 Internal Server Error 响应。

Response 实例

响应的无数据如 HTTP 状态码,响应头文件,或者 Cookies 都被保存在一个叫做 response 的对象中,并传送给浏览器,你可以直接操作这些无数据或者写一些预定义的 helper 方法来处理它们。

状态码(Status Code)

HTTP 状态码 控制着浏览器处理方式,默认为“200 OK”,绝大多数情况下,你并不需要手工的去设置 Response.status ,但是使用 abort() 函数或者返回一个 HTTPResponse 对象的时候,因为它们允许存在任何数值的状态码,为了符合 HTTP 规范,我们应该手动的为其添加规范的 HTTP 状态码。

响应头文件(Response Header)

响应的头文件如 Cache-Control 或者 Location 等都是通过 @Response.set_header() 函数定义的,该函数接受两个参数:一个头文件名称和一个值,名称部分是区分大小写的:

@route('/wiki/page')
def wiki(page):
response.set_header('Content-Language', 'en')
...

绝大多数头文件都仅仅只能定义一次,但是有一些特别的头文件却可以多次定义,这个时候我们在第一次定义时使用 Response.set_header() ,但是第二次定义时,就需要使用 Response.add_header()了:

response.set_header('Set-Cookie','name=value')
response.add_header('Set-Cookie','name1=value1')

Cookies

你可以使用 Request.get_cookie() 访问已经设置了的 Cookie,可以使用 Response.set_cookie() 设置 Cookie:

@route('/hello')
def hello_again(self):
if request.get_cookie('visited'):
return 'Welcome back! Nice to see you again'
else:
response.set_cookie('visited','yes')
return 'Hello there! Nico to meet you!'

Response.set_cookie() 方法接受一些特殊的参数,用来控制 Cookie 的生命周期或者行为,最常见的一些参数如下:

  • max_age : 该 Cookie 最大的生命期(按秒计算,默认为 None)
  • expires : 上个 datetime 对象或者一个 UNIX timestamp(默认为 None)
  • domain : 允许访问该 Cookie 的域名(默认为当前应用的域名)
  • path : 按照路径限制当前 Cookie(默认为 “/“)
  • secure : 限制当前Cookie仅仅允许通过 HTTPS 连接访问(默认为 off)
  • httponly : 阻止浏览器端 Javascript 读取当前 Cookie(默认为 off,需要 Python 2.6 以上)

如果 expires 或者 max_age 都没有设置的放在, Cookie 将在浏览器的会话结束后或者当浏览器关闭时失效,这里还有一些问题是你在使用 Cookie 时需要考虑到的:

  • 大多数浏览器都限制 Cookie 的大小不能超过 4Kb
  • 有一些用户设置了他们的浏览器不接受任何 Cookie,绝大多数搜索引擎也直接忽略 Cookie,你应该保证你的应用在没有 Cookie 时也是可用的
  • Cookie 保存在客户端,并且没有任何加密措施,你存放在 Cookie 中的任何内容,用户都是可访问的,如果有必要的话,攻击者能通过 XSS 漏洞窃取用户的 Cookie,所以,尽可能在不要在 Cookie 中保存机密信息
  • Cookie 是很容易被伪造的,所以,尽可能不要想信 Cookie

就像上面看到的, Cookie 太容易被恶意软件盗取,所以 Bottle 为 Cookie 提供的加密方法,你所需要做的仅仅只是提供了一个密钥,只要能确保该密钥的安全即可,而其导致的结果是,对于未加密的 Cookie,@Request.get_cookie()@ 将返回 None。

@route('/login')
def login():
username = request.forms.get('username')
password = request.forms.get('password')
if check_user_credentials(username, password):
response.set_cookie('account', username, secret='some-sceret-key')
return 'Welcome {}'.format(username)
@route('/restricted')
def restricted_area(self):
username = request.get_cookie('account', secret='some-secret-key')
if username:
return 'Hello {}'.format(username)
else:
return 'You are not logged in.'

另外,Bottle 会自动 pickle 与 unpickle 你存储到已签名的 Cookie 上的数据,这表示你可以向 Cookie 中存储任何可以 pickle 的数据对象,只要其大小不超过 4Kb即可。

访问请求数据(Accessing Request Data)

Bottle 的全局对象 request 提供了对 HTTP相关的无数据如 Cookies, Headers, 或者 POST 表单数据的访问,该对象在任何时候都保存着当前请求的数据,只要其在一个路由的回调函数中访问即可,它甚至还可以在多线程环境中工作。

HTTP 头文件

头文件信息都保存在 Request.header 中,其成员是一个键区分大小写的 HeaderDict 实例:

from bottle import route, request
@route('js_ajax')
def is_ajax():
if request.header.get('X-Requested-With') == 'XMLHttpRequest':
return 'This is an AJAX request'
else:
return 'This is a normal request'

Cookies

Cookie 已一个普通的 dictionary 形式保存在 Request.COOKIES 对象中, Request.get_cookie()@ 方法可以对签名的 Cookie 进行访问,下面示例展示了一个基于 Cookie 的访问计数器:

from bottle import route, request, response
@route('/counter')
def counter():
count = int( request.COOKIES.get('counter', '0'))
count += 1
response.set_cookie('counter',str(count))
return 'You visited this page {} times'.format(count)

查询字符串(Query Strings)

查询字符串常常被用来传递一些小数目的键值对参数到服务器,你可以使用 Request.GET 字典对其进行访问,使用 Request.query_string 来获得整个字符串:

from bottle import route, request, response
@route('/forum')
def display_forum():
forum_id = request.GET.get('id')
page = request.GET.get('page','1')
return 'Forum ID: {} ( Page: {} )'.format(forum_id, page)

POST 表单数据与文件上传

POST 与 PUT 请求中, request 可以包含各种编码方式的数据,使用 Request.forms 对象可以访问普通的 POST 表单数据,文件上传时提交的数据被单独以 cgi.FieldStorage 实例的形式存储在Request.files 中,而 Request.body 按原始数据的方式保存有一个文件对象的数据。

下面是一个文件上传的示例:

<form action"/upload" method="post" enctype="multipart/form-data">
<input type="text" name="name" />
<input type="file" name="data" />
<input type="submit" value="Upload" />
</form>

Bottle 代码

from bottle import route, request
@route('/upload', method = 'POST')
def do_upload():
name = request.forms.get('name')
data = request.files.get('data')
if name and data.file:
raw = data.file.read() #当文件很大时,这个操作将十分危险
filename = data.filename
return "Hello {}! You uploaded {} ({} bytes).".format(name, filename, len(raw))
return "You missed a field"

WSGI 环境

Request 对象将 WSGI 环境数据都以 dictionary 等式保存在 Request.environ 中,允许你像访问字典数据一样访问其值:

route('/my_ip')
def show_ip():
ip = request.environ.get('REMOTE_ADDR')
# 或者 ip = request.get('REMOTE_ADDR')
# 或者 ip = request['REMOTE_ADDR']
return 'Your IP is : {}'.format(ip)

模板(Templates)

Bottle 内置了一个快速且强大的模板引擎,叫作:*SimpleTemplate Engine* ,你可以使用template() 函数 或者 view() decorator 来编译一个模板,你所要作的仅仅只是提供该模板,以及要传送给模板的数据,下面是一个模板的简单示例:

@route('/hello')
@route('/hello/:name'):
def hello(name = 'World')
return template('hello', name = name)

上面的代码将载入 hello.tpl ,然后将 name 传送给该模板,并编译它,再将结果返回给浏览器, Bottle 将在 ./views/ 或者 bottle.TEMPLATE_PATH 设置的路径中搜索模板文件。

view() decorator 允许你返回一组需要传送给模板的数据字典即可,而不需要再重新传送模板名称:

@route('/hello')
@route('/hello/:name')
@view('hello')
def hello(name='World'):
return dict(name=name)

模板语法

模板语法是非常精巧的,其工作原理基本可以说成是:将模板文件中的代码进行正确的缩进处理,以至你不再需要担心块缩进问题:

%if name == 'World':
<h1> Hello {{name}} </h1>
<p> This is a test.</p>
%else:
<h1>Hello {{name.title()}}</h1>
<p>How are you?</p>
%end

缓存

模板被编译之后会缓存至内存中,你可以使用 bottle.TEMPLATES.clear() 去手工清除它们。

插件(Plugins)

这是 Bottle 0.9 版本才有的新功能,插件可以提供 Bottle 核心同有提供的功能集,在“可用的 Bottle 插件列表”:http://bottlepy.org/docs/dev/plugins/index.html 中你可以找到现在可用的插件,你还可以开发自己的 Bottle 插件,比如 sqlite 插件,可以让你可以使用 db 来访问一个到SQLite 数据的链接:

from bottle import route, install, template
from bottle_sqlite import SQLitePlugin
install(SQLitePlugin(dbfile='/tmp/test.db'))

route('/show/:post_id') def show(db, post_id): c = db.execute('SELECT title, content FROM posts WHERE id = ?', (int(post_id),)) row = c.fetchone() return template('show_post', title=row['title'], text=row['content']) route(’/contact’)
def contact_page(): ‘’‘该回调函数不需要任何数据库连接,因为没有 db 关键字, 所以 SQLite插件将完全忽略该回调函数’‘’ return template(‘contact’)

在整个应用中安装插件

插件可以被安装到整个应用中,或者仅仅只针对某几个路由安装,绝大多数插件都被安装到整个应用中,以为所有路由服务。要安装一个插件,只需要将插件的名称作为第一个参数传递给 install()函数即可:

from bottle_sqlite import SQLitePlugin
install(SQLitePlugin(dbfile='/tmp/test.db'))

卸载已安装的插件

你可以使用名称,类或者对象来卸载一个已经安装的插件

sqlite_plugin = SQLitePlugin(dbfile='/tmp/test.db')
install(sqlite_plugin)
uninstall(sqlite_plugin) #卸载特定的插件
uninstall(SQLitePlugin) #卸载该类的所的实例
uninstall('sqlite') # 卸载所有具有该名称的插件
uninstall(True) # 一次性卸载所有已安装的插件

插件可以在任何时间安装与卸载,甚至是处理某个请求的回调函数中,每一次已经安装的插件树更新时, 路由缓存都会跟着更新。

与路由绑定的插件安装

route() 的 apply 参数可以指定某个回调函数要安装的插件:

sqlite_plugin = SQLitePlugin(dbfile=’/tmp/test.db’)
@route(’/create’, apply=[sqlite_plugin])
def create(db): db.execute(‘INSERT INTO ….’)

插件黑名单

如果可以使用 route() 方法中的 skip 参数指定插件黑名单,如下:

sqlite_plugin = SQLitePlugin(dbfile='/tmp/test.db')
install sqlite_plugin)
@route('/open/:db', skip=[sqlite_plugin])
def open_db(db):
if db in ['test','test2']:
sqlite_plugin.dbfile = '/tmp/{}.db'.format(db)
return 'Database File Switched to : /tmp/{}.db'.format(db)
abort(404, 'No such database')

插件与子应用

大多数插件都被安装到需要它的具体的应用中,所以,它们不应该影响注册给Bottle 应用的子应用:

root = Bottle()
root.mount(apps.blog, '/blog')
@route.route('/contact', template='contact')
def contact():
return {'email':'contact@example.com')
root.install(plugins.WTForms())

上面的示例代码中,不管我们什么时候 mount 一个子应用到主应用上,主应用都会为子应用设定一个代理,所以上面的 WTForms 插件将只会影响到 ‘/contact’ 路径,但是不会影响到 ‘/blog’ 子应用的所有URL。,但是这处理方式可以使用下面的方法覆盖:

route.mount(apps.blog, '/blog', skip=None)

开发(Development)

上面已经介绍了一些基本的关于 Bottle 的知识,如果你现在想使用 Bottle 开发自己的应用,那么下面这些技巧对于你的项目来说可能很有帮助:

默认应用

Bottle 维护着一份 Bottle 实例的栈,而 route() 其实是对 Bottle.route() 的快捷访问,以这种方法产生的路由都属于默认应用:

@route('/')
def hello():
return 'Hello World'

对于小应用来说,这已经足够了,但是随着应用的不断增大,这种方法显然不容易维护,所以我们可以使用子应用,将整个项目的功能细分:

blog = Bottle()
@blog.route('/')
def index():
return 'This is blog Index page'

将应用分离之后,程序的维护性提高了很多,而且可重用性也提高很多,其它的开发人员就可以放心的从你的模块中导入应用程序对象,并使用 Bottle.mount() 将你的应用与他们的应用整全到一起。另外一种替代方法,你可以使用 应用栈 ,这让你可以在所有子应用中都使用默认的 route 方法:

default_app.push()
@route('/')
def hello():
return 'Hello World'
app = default_app.pop()

app() 与 default_app() 都是 AppStack 的实例,并且实现的类 Stack的API,你可以 Push 或者 Pop应用到这个 stack 中。

Debug 模式

在开发的前期,Debug 模式将非常有助于你的开发:

bottle.debug(True)

在这种模式下,Bottle 可以提供更多的 debugging 信息,即使程序出现一个错误,它同时还关闭了一些优化功能,添加了一些配置的检测功能,下面是该模式不完整的功能列表:

  • 默认错误页面将返回一个对该错误的跟踪
  • 模板不会被缓存
  • 插件将立即被安装

自动重载

在开发的过程,你可能需要经常修改你的代码,又经常需要重启你的服务器以更新这些修改,Bottle 提供了一个自动重载的工具,这使得你对任何一个应用中的文件的修改都会被及时的更新到运行中的应用中:

from bottle import run
run(reloader=True)

reloader 是这么工作的: 主进程并不会启动服务器,但是它会按照同样的参数创建一个子进程,这使得所有模块级的代码都会被运行两次。子进程的运行环境中会有一个叫作os.environ['BOTTLE_CHILD'] = True 的参数,当任何一个已经加载的模块有修改时,子进程会被停止,然后由主进程重新开启新的子进程,对模板的修改将不会引发一次重载。

重载是基于是否可以关闭子进程的,如果你运行在 Windows 或者任何其它不支持 signal.SIGINT 的操作系统上时,@signal.SIGTERM@ 被用来终止子进程。

部属(Deployment)

Bottle 默认是运行在内置的 wsgiref WSGIServer上的,该无线程服务器对于开发来说再好不过了,但是对于日渐壮大的应用或者对于实际部属来说,并不是最好的选择。

多线程服务器

提高效率的最快速的办法,就是将应用部属到一个多线程的服务器或者类似 Asynchronous WSGI 的服务器上,比如 paste 或者 cherrypy ,并且告诉 Bottle 以这些服务器启动,而不是自己内置的服务器。

bottle.run(server='paste')

Bottle 支持很多服务器,下面列举的并不是所有的:

名称 主页 介绍
cgi 以CGI脚本运行
flup http://trac.saddi.com/flup 以 FastCGI 进程运行
gae http://code.google.com/appengine/docs/python/overview.html Google App Engine 部属
wsgiref http://docs.python.org/library/wsgiref.html 默认为单线程的服务器
cherrypy http://www.cherrypy.org/ 多线程服务器
paste http://pythonpaste.org/ 多线程服务器
rocket http://pypi.python.org/pypi/rocket 多线程服务器
gunicorn http://pypi.python.org/pypi/gunicorn 部分用 C 编写
fapws3 http://www.fapws.org/ Asynchronous,基于C 开发
tornado http://www.tornadoweb.org/ Asynchronous,服务了部分 FaceBook 的服务
twisted http://twistedmatrix.com/ Asynchronous
diesel http://dieselweb.org/ Asynchronous,基于 Greenlet
meinheld http://pypi.python.org/pypi/meinheld Asynchronous,部分基于 C 开发
bjoern http://pypi.python.org/pypi/bjoern Asynchronous,非常快,基于C开发
auto 自动选择一个可用的 服务器

完整的服务器名称可使用 server_names 变量获得,如果 Bottle 还没有提供你最喜欢的服务器,那你可以手工的使用你的服务器启动它:

from paste import httpserver
httpserver.serve(bottle.default_app(), host='0.0.0.0', port = 80)

多服务器进程

一个 Python 进程只能使用到一个 CPU,即时服务器硬件有多个CPU,你可以在不同的端口中启动多个应用,每一个应用使用一个 CPU,然后使用分流服务器对访问进行分流,比如 Apache mod_wsgi 或者 Nginx 等都可以作为前端分流服务器。

以前一直都是使用CentOS + Apache + MySQL + PHP来做服务器的,一般也都不是自己配置,使用的都是已经配置好现成的,比如很久以前使用的BlueHost的cPanel主机,后来还使用MediaTemple的VPS,这些都是Ready to Run的服务器,只需要付了钱之后立马就可以使用的,后来发现Linode不错,就买这个的VPS,而这个只是一个纯粹的裸体,买了之后只一个纯粹的操作系统,所有都得自己安装,这篇文章旨在介绍一下如何在CentOS上面配置Nginx + MySQL + PHP + FastCGI服务器,我在Linod上面使用的方案,现在公司内部的服务器使用的也是这个方案。

安装必需的包

因为CentOS并没有全部包含Nginx运行需要的很多环境,所以我们需要安装EPEL(Extra Packages for Enterprise Linux)支持,这是来自Fedora 的一个项目,使用下面的命令:

 rpm -Uvh http://download.fedora.redhat.com/pub/epel/5/i386/epel-release-5-3.noarch.rpm

然后使用下面的命令升级你的CentOS系统,并安装Nginx网页服务器、PHP以及编译工具:

yum update
yum install nginx php-cli php make automake gcc gcc-c++ spawn-fcgi wget
chkconfig --add nginx
chkconfig --level 35 nginx on
service nginx start

安装进程完成并且成功启动之后,你就可以访问 http://localhost 或者 http://127.0.0.1 访问服务器,以查看是否安装成功,如果成功你将会看到Nginx服务器的默认页面。

配置你的网站

服务器已经成功安装了,现在需要配置你的网站,在这里我以本站为例来说明如何配置一个网站,首先,我们需要一个用来存放网站程序以及公开访问访问的目录,还需要一人上日志存放的目录,而我把同一个网站的所有这些目录都存放在同一个父目录“/srv/www/www.solocrab.com/”中,所以,我们首先需要创建这些目录:

先创建网页存放用的目录:

mkdir -p /srv/www/www.solocrab.com/public_html

然后创建用来存放目录文件的目录:

mkdir -p /srv/www/www.solocrab.com/logs

最后将这个目录丢给用户组nginx中的用户nginx:

chown -R nginx:nginx /srv/www/www.solocrab.com

然后我们需要为这个网站创建Nginx服务器的配置文件,在这一步中,我创建了两个目录,分别为:

  1. 用来存放可用网站配置文件的:/etc/nginx/sites-available
  2. 用来存放已启用网站配置文件的:/etc/nginx/sites-enabled

使用如下命令:

mkdir /etc/nginx/sites-available
mkdir /etc/nginx/sites-enabled

目录创建完成之后,我们还需要编辑 nginx 服务器的主配置文件,让它将已启用网站配置文件目录的配置文件引用进主配置文件,打开 /etc/nginx/nginx.conf 文件,在 include /etc/nginx/conf.d/*.conf 这一行的下一行中,输入下面这两行内容:

#Load virtual host configuration files.
include /etc/nginx/sites-enabled/*;

创建我们为 solocrab创建一个配置文件:

文件:/etc/nginx/sites-available/www.solocrab.com

server {
listen 80;
server_name note.solocrab.com;
access_log /srv/www/note.costony.com/logs/access.log;
error_log /srv/www/note.costony.com/logs/error.log;
location / {
root /srv/www/note.costony.com/public_html;
index index.html index.htm;
}
location ~ .php$ {
include /etc/nginx/fastcgi_params;
fastcgi_pass 127.0.0.1:9000;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME /srv/www/note.costony.com/public_html$fastcgi_script_name;
}
}</pre>

完成之后,我们需要启用这个网站配置,所以,需要将这个网站配置信息引用进入Nginx的主配置文件中,使用下面这几个命令:

cd /etc/nginx/sites-enabled/
ln -s /etc/nginx/sites-available/note.costony.com
service nginx restart

你可以在网站的目录中创建一个测试用的HTML文件来查看配置是否成功。

配置 spawn-fcgi

我们现在需要让这个网站能够运行PHP脚本,首先创建下面这个文件:

/usr/bin/php-fastcgi

#!/bin/sh
/usr/bin/spawn-fcgi -a 127.0.0.1 -p 9000 -C 6 -u nginx -g nginx -f /usr/bin/php-cgi

创建完成之后还需要让这个文件可执行:

bc chmod a+x /usr/bin/php-fastcgi

为php-fastcgi创建一个初始化脚本:

文件:/etc/rc.d/init.d/php-fastcgi

#!/bin/sh
#
# php-fastcgi - Use PHP as a FastCGI process via nginx.
#
# chkconfig: - 85 15
# description: Use PHP as a FastCGI process via nginx.
# processname: php-fastcgi
# pidfile: /var/run/php-fastcgi.pid
# Source function library.
. /etc/rc.d/init.d/functions
# Source networking configuration.
. /etc/sysconfig/network
# Check that networking is up.
[ "$NETWORKING" = "no" ] && exit 0
phpfastcgi="/usr/bin/php-fastcgi"
prog=$(basename php-cgi)
lockfile=/var/lock/subsys/php-fastcgi
start() {
[ -x $phpfastcgi ] || exit 5
echo -n $"Starting $prog: "
daemon $phpfastcgi
retval=$?
echo
[ $retval -eq 0 ] && touch $lockfile
return $retval
}
stop() {
echo -n $"Stopping $prog: "
killproc $prog -Q
retval=$?
echo
[ $retval -eq 0 ] && rm -f $lockfile
return $retval
}
restart() {
configtest || return $?
stop
start
}
reload() {
configtest || return $?
echo -n $"Reloading $prog: "
killproc $prog -HUP
RETVAL=$?
echo
}
force_reload() {
restart
}
rh_status() {
status $prog
}
rh_status_q() {
rh_status >/dev/null 2>&1
}
case "$1" in
start)
rh_status_q && exit 0
$1
;;
stop)
rh_status_q || exit 0
$1
;;
restart|configtest)
$1
;;
reload)
rh_status_q || exit 7
$1
;;
force-reload)
force_reload
;;
status)
rh_status
;;
condrestart|try-restart)
rh_status_q || exit 0
;;
*)
echo $"Usage: $0 {start|stop|status|restart}"
exit 2
esac

然后运行下面这些命令,让其在系统启用时就执行:

chmod 755 /etc/rc.d/init.d/php-fastcgi
/etc/rc.d/init.d/php-fastcgi start
chkconfig --add php-fastcgi
chkconfig --level 35 php-fastcgi on

测试以FastCGI方式运行的PHP

现在我们可以创建一个PHP测试文件来查看是否已经配置成功:

文件:/srv/www/www.solocrab.com/public_html/phpinfo.php

<?php phpinfo(); ?>

安装 MySQl 数据库服务器

使用下面的命令安装 MySQL 数据库服务器:

yum update
yum install mysql-server
/sbin/chkconfig --levels 235 mysqld on

然后让MySQl服务器在系统启动时自动启动:

service mysqld start

安装完成之后,我们可以对MySQL进行一次安装设置,使用如下命令:

mysql_secure_installation

一些常见的问题

有的时候我们在运行chkconfig或者service命令时,会得到类似下面这样的错误:

bash: service: command not found

在本文章中,如果遇到这个错误,只需要把相关的命令改成:

/sbin/service

或者:

/sbin/chkconfig

刚在在看 Python 的 string 模块,在里面看到了下面这两个函数:

def translate(s, table, deletions=""):
if deletions or table is None:
return s.translate(table, deletions)
else:
return s.translate(table + s[:0])
_idmapL = None
def maketrans(fromstr, tostr):
if len(fromstr) != len(tostr):
raise ValueError, "maketrans arguments must have same length"
global _idmapL
if not _idmapL:
_idmapL = list(_idmap)
L = _idmapL[:]
fromstr = map(ord, fromstr)
for i in range(len(fromstr)):
L[fromstr[i]] = tostr[i]
return ''.join(L)

这两个函数都是联系在一起使用的,首先由 maketrans(fromstr,tostr) 函数来创建用来 translate 的 table,然后我们才能对某一个字符串使用 translate 函数,而 translate 函数其实就是对内置的字符串函数 translate 的调用。

在更详细的了解 maketrans 函数之前,我们可以先来了解这个函数所使用到的一些数据,由 string.py 这个文件可以找到 global _idmapL 默认的值为 None,然后使用了下面这一行代码:

_idmapL = list(_idmap)

继续往上追踪,找到了下面这三行代码:

l = map(chr, xrange(256))
_idmap = str('').join(l)
del l

这个时候,我们就可以知道,_idmapL 最终会是什么样的值的了,其值如下:

['x00', 'x01', 'x02', 'x03', 'x04', 'x05', 'x06', 'x07', 'x08', 't', 'n', 'x0b', 'x0c', 'r', 'x0e', 'x0f', 'x10', 'x11', 'x12', 'x13', 'x14', 'x15', 'x16', 'x17', 'x18', 'x19', 'x1a', 'x1b', 'x1c', 'x1d', 'x1e', 'x1f', ' ', '!', '"', '#', '$', '%', '&', "'", '(', ')', '*', '+', ',', '-', '.', '/', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', ':', ';', '<', '=', '>', '?', '@', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '[', '', ']', '^', '_', '`', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '{', '|', '}', '~', 'x7f', 'x80', 'x81', 'x82', 'x83', 'x84', 'x85', 'x86', 'x87', 'x88', 'x89', 'x8a', 'x8b', 'x8c', 'x8d', 'x8e', 'x8f', 'x90', 'x91', 'x92', 'x93', 'x94', 'x95', 'x96', 'x97', 'x98', 'x99', 'x9a', 'x9b', 'x9c', 'x9d', 'x9e', 'x9f', 'xa0', 'xa1', 'xa2', 'xa3', 'xa4', 'xa5', 'xa6', 'xa7', 'xa8', 'xa9', 'xaa', 'xab', 'xac', 'xad', 'xae', 'xaf', 'xb0', 'xb1', 'xb2', 'xb3', 'xb4', 'xb5', 'xb6', 'xb7', 'xb8', 'xb9', 'xba', 'xbb', 'xbc', 'xbd', 'xbe', 'xbf', 'xc0', 'xc1', 'xc2', 'xc3', 'xc4', 'xc5', 'xc6', 'xc7', 'xc8', 'xc9', 'xca', 'xcb', 'xcc', 'xcd', 'xce', 'xcf', 'xd0', 'xd1', 'xd2', 'xd3', 'xd4', 'xd5', 'xd6', 'xd7', 'xd8', 'xd9', 'xda', 'xdb', 'xdc', 'xdd', 'xde', 'xdf', 'xe0', 'xe1', 'xe2', 'xe3', 'xe4', 'xe5', 'xe6', 'xe7', 'xe8', 'xe9', 'xea', 'xeb', 'xec', 'xed', 'xee', 'xef', 'xf0', 'xf1', 'xf2', 'xf3', 'xf4', 'xf5', 'xf6', 'xf7', 'xf8', 'xf9', 'xfa', 'xfb', 'xfc', 'xfd', 'xfe', 'xff']

同样, L 的值也是上面这样的。

然后下面的一行:

fromstr = map(ord,fromstr)

会将传入的参数 fromstr 的值转换成为 ASCII 码的一个 list,比如我们传入的 fromstr 为 fromstr = 'hi python',那么转换之后会得到下面这个值:

>>> fromstr = map(ord,'hi python')
>>> fromstr
[104, 105, 32, 112, 121, 116, 104, 111, 110]

接着的一个 for 循环:

for i in range(len(fromstr)):
L[fromstr[i]] = tostr[i]
return ''.join(L)

这会将系统默认的 ASCII 码对应的字符转换成为新的 tostr 中的字符,比如我们传入的字符还是上面所示的“hi python“,而 tostr = 'o javaeye' 也得到了上面的那么一个ASCII码的LIST,这个时候,本来L104对应的字符应该是 “h”,可是我们在这里将其换成了 tostr 中的第一个元素,即 “o”,以此类推荐,所以 maketrans 最后返回的结果将是一个被修改了的 ASCII 字符串,它是将原来 fromstr 中所对应的字符的ASCII码的值更改为 tostr 中的那一个字符。

那么,如果一直按上面所说的,就会得到下面这样的运行结果:

translate('hi python',table)
'o javaeye'

在循环 dictionary(字典)型数据时,键与值(Key , Value)可以在一个 for 循环中使用 dictinary 的iteritems() 函数同时检索到,如下:

>>> knights = {'gallahad': 'the pure','robin':'the brave'}
>>> for k,v in knights.iteritems(): print k,v
...
gallahad the pure
robin the brave

当我们循环一个序列时,元素的位置与元素值本身也可以同时被检索到,这个时候我们就需要使用enumerate() 函数:

>>> seq = ['tic', 'tac', 'toe']
>>> for i,v in enumerate(seq): print i,v
...
0 tic
1 tac
2 toe

如果同时将两个序列进行检索,我们可以使用 zip() 函数将这两个序列进行配对,然后再在同一个 for 循环中检索:

>>> questions = ['name', 'quest', 'favorite color']
>>> answers = ['costony', 'the holy grail', 'blue']
>>> for q,a in zip(questions, answers):
... print 'What is your {0}? It is {1}.'.format(q,a)
...
What is your name? It is costony.
What is your quest? It is the holy grail.
What is your favorite color? It is blue.

在Python中,有三个函数是我们编程过程中经常会使用到的,在很多时候可以帮助我们解决很多麻烦的事情,它们分别是:@filter(function, sequence)@,@map(function,sequence)@和@reduce(function,sequence)@。

filter(function,sequence)

将 sequence 中的所有元素都作为参数传递给 function 参数指定的函数,如果该函数返回 True,则将该元素追加到结果集中,当整个 sequence 全部遍历完了之后,返回结果集,示例如下(该示例将找到出所有不能同时被 2 和 3 整除的数:

>>> def f(x): return x % 2 != 0 and x % 3 != 0
...
>>> filter(f,range(2,25))
[5, 7, 11, 13, 17, 19, 23]

map(function, sequence)

该函数将 sequence 中的所有元素都传递给 function 所指定的函数,将处理得到的结果加入以 sequence 中元素的顺序加入到结果集中,并返回结果集,如下所示,将求出 sequence 序列中所有数的立方:

>>> def cube(x): return x*x*x
...
>>> map(cube, range(1,11))
[1, 8, 27, 64, 125, 216, 343, 512, 729, 1000]

reduce(function, sequence)

该函数将只返回一个结果,其处理方式是:首先是将 sequence 中的第 0 位 和第 1 位元素传递给 function 所指定的函数,得到的结果作为下一次运算的一个参数,并且把 sequence 中的下一位元素作为另一个参数,如此直至 sequence 的所有元素都参与了运算,之后返回结果,如下示例将求出序列中的所有数的总和:

>>> def add(x,y): return x+y
...
>>> reduce(add,range(1,11))
55

使用 lambda 函数

很多时候我们传递给上面所说的三个函数的 函数 都是非常简单的,这个时候我们可以使用的 lambda 函数,比如上面三个示例我们可以像下面这么写:

>>> filter(lambda x: x % 2 != 0 and x % 3 != 0, range(2,25))
[5, 7, 11, 13, 17, 19, 23]
>>> map(lambda x: x*x*x, range(1,11))
[1, 8, 27, 64, 125, 216, 343, 512, 729, 1000]
>>> reduce(lambda x,y:x+y, range(1,11))
55

而对于 lambda 函数,我们还可以把多个lambda 函数都保存到一个字典中,如下面这样的方法去使用:

>>> lfs = {'check':lambda x: x % 2 != 0 and x % 3 != 0,
... 'cube':lambda x: x*x*x, 'add': lambda x,y: x+y}
>>> filter(lfs['check'], range(2,25))
[5, 7, 11, 13, 17, 19, 23]
>>> filter(lfs['cube'], range(1,11))
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
>>> reduce(lfs['add'], range(1,11))
55

SQLAlchemy 是下一代的 Python Object Relational 映射器,有了这个东西,我们就可以更少的接触数据库的底层了,我现在基本上都在使用Flask,而Flask 里面也有SQLAlchemy的封装:flaskext.sqlalchemy ,使得我们在开发Flask应用的时候更方便的操作数据库,这里就来记录一个最通用的程序数据库设计中使用Flask SQLAlchemy的示例。

整个程序代码如下(感觉我这里看着不方便,就把下面的代码保存到任何一个现代的IDE或者程序编辑器里面都应该是可以看得很清楚的,我这里是因为Textile会把空行当作代码的结束,所以只能全部写到一起了):

#!/usr/bin/env python
# encoding: utf-8
"""
models.py
Created by Pan Tao on 2011-09-02.
Copyright (c) 2011 CosTony.Com. All rights reserved.
"""
from flask import Flask
from flaskext.sqlalchemy import SQLAlchemy
from datetime import datetime
app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:////tmp/models.db'
app.config['SQLALCHEMY_BINDS'] = {
'user' : 'sqlite:////tmp/user_db.db',
'appmeta' : 'sqlite:////tmp/appmeta.db'
}
db = SQLAlchemy(app)
tags = db.Table('tags',
db.Column('tag_id', db.Integer, db.ForeignKey('tag.id')),
db.Column('post_id', db.Integer, db.ForeignKey('post.id'))
)
class Role(db.Model):
id = db.Column(db.Integer, primary_key = True)
name = db.Column(db.String(20), unique = True)
def __init__(self, name):
self.name = name
def __repr__(self):
return '<Role %r>' % self.name
class User(db.Model):
__bind_key__ = 'user'
id = db.Column(db.Integer, primary_key = True)
username = db.Column(db.String(80), unique = True)
email = db.Column(db.String(120), unique = True)
role_id = db.Column(db.Integer, db.ForeignKey('role.id'))
role = db.relationship('Role',
backref = db.backref('users', lazy='dynamic'))
def __init__(self, username, email, role):
self.username = username
self.email = email
self.role = role
def __repr__(self):
return '<User %r>' % self.username
class Post(db.Model):
id = db.Column(db.Integer, primary_key = True)
title = db.Column(db.String(80))
body = db.Column(db.Text)
pub_date = db.Column(db.DateTime)
author_id = db.Column(db.Integer, db.ForeignKey('user.id'),info={'bind_key': 'user'})
author = db.relationship('User',
backref=db.backref('posts', lazy='dynamic'))
category_id = db.Column(db.Integer, db.ForeignKey('category.id'))
category = db.relationship('Category',
backref=db.backref('posts', lazy='dynamic'))
tags = db.relationship('Tag', secondary=tags,
backref = db.backref('posts', lazy='dynamic'))
def __init__(self, title, body, category, tags, author, pub_date = None):
self.title = title
self.body = body
if pub_date is None:
pub_date = datetime.utcnow()
self.pub_date = pub_date
self.category = category
self.tags = tags
self.author = author
def __repr__(self):
return '<Post %r>' % self.title
class Category(db.Model):
id = db.Column(db.Integer, primary_key = True)
name = db.Column(db.String(50))
def __init__(self, name):
self.name = name
def __repr__(self):
return '<Category %r>' % self.name
class Tag(db.Model):
id = db.Column(db.Integer, primary_key = True)
name = db.Column(db.String(20), unique = True)
def __init__(self,name):
self.name = name
def __repr__(self):
return '<Tag %r>' % self.name
def init_db():
db.drop_all()
db.create_all()

最开始,我们把需要使用的一些模块都导入进来,在这里,我导入了Flask, flaskext.sqlalchemy 中的 SQLAlchemy(注意这个和原生的 SQLAlchemy有一点点不一样的),然后 datetime 中导入 datetime(这个其实不导入也没有关系,只需要把 Post 类中的创建时间的属性删除掉就行了。

然后我创建了一个Flask实例(这个在Flask SQLAlchemy中是必须的,需要我们在创建 FlaskSQLAlchemy 的时候不指定 Flask app,那我们也必须在之后使用 Flask SQLAlchemy 实例之前,初始化它的Flask app。

app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:////tmp/models.db'
app.config['SQLALCHEMY_BINDS'] = {
'user' : 'sqlite:////tmp/user_db.db',
'appmeta' : 'sqlite:////tmp/appmeta.db'
}

上面这一段代码中,我对 Flask app 进行的配置,配置项为下面两个:

  1. SQLALCHEMY_DATABASE_URI : 这个是标准的 SQLAlchemy 数据库地址字符串,我在这里使用的是 SQLite 数据来测试(对于这个Python内置的数据库用起来还真方便)
  2. SQLALCHEMY_BINDS :在这个设置里面,我设置了两个属性,一个是用来保存程序中 user 数据的数据库,我把 user 表单独于其它表放在另外一个数据库中了,这个其实在很小的程序里没有啥用,我这里只是为了尽可能把Flask SQLAlchemy的入门教程讲明白哈。appmeta 被保存在另一个数据库中,另外,当我们初始化数据库之后,还会再创建一个 models 数据库,这个是把其它未指定的列都放在这个数据库里面的。

接着我就来始定义我这个程序里面需要的几个类了:

  • Role :用户组
  • User :用户类
  • Post :文章类
  • Category :文件分类的类
  • Tag :标签

上面这些类的关系如下面这样的:

  • Role -> User :一对多
  • User -> Post :一对多
  • Category -> Post : 一对多
  • Tag -> Post : 多对多

在一对多的实例中,比如Role -> User中,User中有一个 role_id 属性来保存这个用户所属的用户组的ID,然后有一个关系申明,这样可以让Role 对象 role 通过 role.users访问到这个用户组下面所有的用户数据。

而对于多对多的情况,如 Tag -> Post,我在最开始创建了一个表:tags,这个表用来保存Tag 与 Post 之间的关联关系,然后需要在Post类中申明一个 secondary 属性,表示关系是保存在那个表里面的。

最后就是在User类里面有一个不一样的地方,就是有一个 __bind_key__ = 'user' 这个属性,这里其实就是申明这个类的数据要使用 key 为 ”user“ 的数据库,这样的话,我们就需要在与 User 这个类有关联的类中,显示的申明 User 类的’bind_key’为’user‘。

最后面的那一个 init_db() 函数是用来初始化数据库的,如果未修改我代码里面的配置,那么在系统的 /tmp 目录下将会生成下面这些文件:

/tmp
/appmeta.db
/user_db.db
/models.db

大体上就这样的吧,反正自己以后再来看这个我还能看懂,所以就这样了吧^

在 Python 中有这么一个 property 函数,一直没弄明白是怎么回事儿,今天就好好的研究了一下下,我的理解是这样的,如下面这一段代码:

class C(object):
def __init__(self): self._x = 1
def getx(self): return self._x * 2
def setx(self,value): self._x = value/2
def delx(self): self._x = 1
x = property(getx,setx,delx,"I'm the property.")

上面这一段代码的最后一行用了一个 @property@ 函数,这一段代码的意思是:当我们给 class: C(object) 的实例的成员 x 赋值时,需要首先调用 setx(self,value) 函数,取其值时,需要先调用getx(self) 函数,删除它时,需要先调用 delx(self) 函数。

所以我们会有下面这样的运行结果:

>>> c = C()
>>> c.x
2
>>> c.x = 3
>>> c.x
2
>>> c._x
1
>>> del c.x
>>> c.x
2
>>>

我创建了一个 C 的实例,名为 ”c“,当我去调用 c.x 时,首先运行了 getx 函数,所以返回值是 1 * 2 ,也就是 2 了,而我给 c.x 赋值为 3,这个时候会运行 setx 函数,所以这个时候 c._x 的值变为了 3 / 2 ,其运算结果是 1,所以我再一次取 c.x 的值时,出现的并不是 3,而是 1 * 2 = 2。而我最后面删除 c.x ,但是并没有删除掉,而是重新为 c._x 赋值。

以前只知道使用下面这样的代码来创建我的Flask应用:

from flask import Flask
app = Flask(__name__)

不过从下面的Flask的 init 函数可以看出,其实还有好几个属性是可以在创建应用的时候就指定的:

 def __init__(self, import_name, static_path=None, static_url_path=None,
static_folder='static', template_folder='templates'):
_PackageBoundObject.__init__(self, import_name,
template_folder=template_folder)
if static_path is not None:
from warnings import warn
warn(DeprecationWarning('static_path is now called '
'static_url_path'), stacklevel=2)
static_url_path = static_path
if static_url_path is not None:
self.static_url_path = static_url_path
if static_folder is not None:
self.static_folder = static_folder
....

从上面的代码可以看出,在标准的 Flask 应用目录结构上面还是可以修改的,标准的目录结构如下:

/application
/app.py
/static
/style.css
/....
/templates
/layout.html
/index.html
/....

如果是这样的话,那么在使用@url_for(‘static’,filename=‘style.css’)@函数时,Flask会发送上面static目录下style.css文件的内容,但是如果我们不想把静态文件放在 static 这个目录下,而是 misc 这个目录下面,那么可以是这样的创建Flask 应用

app = Flask(__name__,static_folder='misc')

这个时候,@url_for(‘static’,filename=‘style.css’)@ 这个函数发送的文件将是 /misc/style.css 。同样,我们也可以这样指定 templates 的目录为其它的或者更加适合自己的风格的,当然咯,做为一个Python程序员,不建议把个人风格带到程序里面去。

Flask 是一个十分小巧的Python Web 框架,本文是一篇最简单的 Flask 入门教程,本文所基于的前提是你的电脑已经安装了Python和Python Easy_Install 工具,并且是连着网的。

安装 Flask

安装Flask就像安装其它的Python包一样,使用 easy_install 工具:

#easy_install Flask

如果你是将Flask完整的下载了下来,刚可以使用其自己提供的安装工具安装:

#python ./setup.py install

创建最简单的 Flask 程序

一个标准的 Flask 程序具有如下的目录结构:

/yourapplication
/app.py
/static
/style.css
/...
/templates
/layout.html
/index.html
/...

其中 app.py 不是必须的,我们可以取任何一个合理的文件名,当然,我本示例中,我们将把所有的代码都写在这个文件里面,static 文件夹可以保存一些静态文件,如CSS或者JS文件,而templates刚保存模板文件。

app.py

我们现在创建一个小程序,当用户访问:http://127.0.0.1:5000/ 时,页面上面简单的返回:Hello Flask文字,访问:http://127.0.0.1:5000/flask 时返回 “This page’s path is /flask 文字”,代码如下:

#!/usr/bin/env python
# encoding: utf-8
from flask import Flask
app = Flask(__name__)
@app.route('/')
def helloflask():
return "Hello Flask"
@app.route('/flask')
def flask_page():
return "This page's path is /flask"
if __name__ == "__main__":
app.run()

将上面代码复制到 app.py 文件中,直接运行之:@python app.py@ 即可,这个时候我们去浏览器里面访问上面提到的两个地址,就可以得到想要的结果。

示例程序详解

在上面的示例中,我们做了下面这些事情:

  1. from flask import Flask是从flask中导入Flask,然后我们再使用其提供的方法创建了一个实例@app = Flask(name)@
  2. 接着我们设置两个路径@/@和@/flask@,让这两个路径分别对应相应的处理方法@helloflask()@和@flask_page()@,这两个方法分别返回我们需要的文字。
  3. 接着最后我们一个 if 语句,表示,如果是直接运行该文档,刚创建服务并运行这个程序。

更大的程序

如果你想创建更大的程序,刚可以使用Session,Render_Template等等更高级的模块,你可以访问Flask官方网站以获得更多的帮助:Flask Pocoo

Flask使用Blueprint让应用实现模块化,在Flask中,Blueprint具有如下属性:

  • 一个应用可以具有多个Blueprint
  • 可以将一个Blueprint注册到任何一个未使用的URL下比如 “/”、“/sample”或者子域名
  • 在一个应用中,一个模块可以注册多次
  • Blueprint可以单独具有自己的模板、静态文件或者其它的通用操作方法,它并不是必须要实现应用的视图和函数的
  • 在一个应用初始化时,就应该要注册需要使用的Blueprint

但是一个Blueprint并不是一个完整的应用,它不能独立于应用运行,而必须要注册到某一个应用中。

Blueprint 的概念

简单来说,Blueprint 是一个存储操作方法的容器,这些操作在这个Blueprint 被注册到一个应用之后就可以被调用与招待,Flask 可以通过Blueprint来组织URL以及处理请求。

一个简单的 Blueprint 示例:sample.py

#!/usr/bin/env python
# encoding: utf-8
from flask import Blueprint
sample = Blueprint('sample',__name__)
@sample.route('/')
@sample.route('/hello')
def index():
return "This page is a blueprint page"

在 app 应用中注册我们的Blueprint:app.py

#!/usr/bin/env python
# encoding: utf-8
from flask import Flask
from sample import sample
app = Flask(__name__)
app.register_blueprint(sample)
if __name__ == "__main__":
app.run()

上面的代码在一个名为 app 的应用中注册一个名为 sample 的Blueprint,现在我们运行这个应用,则可以通过我们在Blueprint中定义的方法来访问它并获得 This page is a blueprint page的返回结果:

$python app.py

我们需要访问的网址是:http://127.0.0.1:5000 或者 http://127.0.0.1:5000/hello 都可以得到同样的结果。

但是如果你希望所有的 Blueprint 的请求都基于某一个固定的URL之后,刚我们可以在注册的时候指定其根路径的URL,比如我们想使用 http://127.0.0.1:5000/sample 这个地址来访问 sample 这个 Blueprint,刚可以使用下面这样的注册方法:

app.register_blueprint(sample,url_prefix='/sample')

创世纪:1991

  1. 芬兰大学生Linus Torvalds说,要有个386上的自由操作系统,于是有了Linux。(1991)

早期的碰撞反应:1992 – 1997

  1. 英国大学生Owen Le Blanc说,连fdisk和统一的软件安装来源都没有的操作系统太坑爹了,于是有了MCC Interim Linux,世界上第一个Linux发行版。(1992)
  2. 英国大学生Peter MacDonald说,作为一个操作系统,至少需要在内核基础上绑定TCP/IP和X窗口这样的基本功能,于是有了Softlanding Linux System(SLS)。(1992)
  3. 美国大学生Patrick Volkerding说,SLS维护的不好,于是有了Slackware。(1993)
  4. 美国大学生Ian Murdock说,SLS维护的不好,而且我们需要一个秉承Linux和GNU的开放精神的发行版,于是有了Debian。(1993)
  5. 德国的四个数学系大学生Roland Dyroff,Thomas Fehr,Burchard Steinbild和Hubert Mantel说,我们需要一个德文版的Slackware,于是有了S.u.S.E。(1994)
  6. 美国软件工程师Marc Ewing和年轻的创业者Robert “Bob” Young说,Linux可以为企业提供服务,于是有了Red Hat(红帽)。(1994)
  7. 全球各个学院的Geek们陆续发布了Linux Universe,DILINUX,Monkey等发行版,只是它们都很短命。(1995-1997)

宇宙大爆炸:1998-2003

  1. 美国创业者D. Jeff Dionne和Kenneth Albanowski说,我们需要为摩托罗拉DragonBall系列开发一个发行版,于是有了uClinux。(1998)
  2. 日本工程师Scott Stone说,我们要为亚洲用户们做一个红帽定制版,于是有了TurboLinux。(1998)
  3. 费米实验室说,红帽很好,但我们需要做一些定制,于是有了Fermi Linux(1998)。
  4. 法国大学生Gael Duval说,我要让红帽对于新用户来说很好用,于是有了Mandrake,也就是现在的Mandriva。(1998)
  5. 中国程序员邓煜、廖生苗和李凌说,我们要有完全中文内核的Linux,于是有了蓝点。(1999)
  6. 美国程序员Daniel Robbins说,我们需要一个没有预编译的二进制包,用户可以需要什么加什么的发行版,于是有了Enoch Linux,也就是后来的Gentoo。(1999)
  7. 加拿大软件公司Corel说,Linux也许能够帮助我们的软件扩展更多用户,于是有了Corel Linux Desktop,也就是后来的Xandros。(1999)
  8. 德国某ISP的工程师说,我们需要一个廉价的、有防火墙和杀毒等功能的网络防护系统,于是有了Astaro Security Linux(现在的Astaro Security Gateway)。(1999)
  9. 荷兰程序员Gerard Beekmans说,我们需要一个用户能够完全自定义并掌控的操作系统,于是有了Linux from Scratch。(1999)
  10. 苏格兰音乐家兼程序员Jay Klepacs说,多媒体人需要一个能够替代Windows和Mac OS的操作系统,于是有了Peanut Linux,也就是现在的aLinux。(1999)
  11. 中科院软件研究所说,我们要有自主产权的国产操作系统,于是有了红旗Linux。(1999)
  12. 美国系统管理员Ryan Finnie说,我们需要为系统管理员们做一个专门用来系统、文件修复的发行版工具盘,于是有了Finnix。(2000)
  13. 奥地利(德国)电子工程师Klaus Knopper说,我们需要一个可以在CD或U盘上就能运行的操作系统,于是有了Knoppix,也有了Live CD和Live USB。(2000)
  14. 瑞典程序员Per Lidén说,我们需要一个能够贯彻UNIX的KISS原则的、基于tar.gz打包机制的发行版,于是有了CRUX。(2000)
  15. 日本的Miracle Linux公司说,我们需要一个能够充分支持Oracle数据库的发行版,于是有了Miracle Linux。不过,后来Red Hat对Oracle的支持增强,Miracle Linux表示很尴尬,后来和红旗合作,变成了Asianux。(2000)
  16. 美国创业者Michael Robertson说,我们需要一个能跑Windows软件的Linux,于是有了Lindows。(2001)
  17. 当年Linksys无线路由WRT54G的固件在GPL下开源,一伙开发者说,我们用这个做一个嵌入式发行版在路由器里用吧,于是有了OpenWRT。(2001)
  18. 魔法爱好者Kyle Sallee说,让我们做一个可以像念魔法一样使用的发行版吧,于是有了Sorcerer。(2001)
  19. 捷克程序员Tomas Matejicek说,我们需要一个可以装在口袋里拿来拿去的Slackware,于是有了Slax。(2002)
  20. 美国大学生Aaron Griffin说,Linux发行版应该更轻量,更简单,不需要的全都不要,于是有了Arch Linux。(2002)
  21. 美国工程师Warren Woodford说,SUSE、红帽、Mandriva神马的太难了,于是有了MEPIS。(2003)
  22. 美国开源爱好者John Andrews说,我们需要为那些安度晚年的硬件们设计一个发行版,于是有了Damn Small Linux。(2003)
  23. 澳大利亚工程师Barry Kauler说,我们需要一个用内存就能跑的超轻量级发行版,而且我很爱狗,于是有了Puppy Linux。(2003)
  24. 给Mandrake打包打烦了的Bill Reynolds说,我就是想自己打包源代码自己说了算,于是有了PCLinux。(2003)
  25. 一群系统管理员们说,我们需要一个不用花钱的红帽,于是有了CentOS。(2003)
  26. 红帽说,Red Hat Linux这种桌面服务太累,我不想做了,交给社区吧,于是有了Fedora Core。(2003)

企业、政府、学院、市场、社区:2004 – 2007

  1. 西班牙安达鲁西亚政府的官员说,我们在学校、图书馆、公民活动中心这种公共场所使用Linux吧,于是有了Guadalinex。(2004)
  2. 南非富豪程序员Mark Shuttleworth说,我们应该有个专门针对桌面的Debian衍生版,于是有了Ubuntu。(2004)
  3. 台湾的国家高性能计算中心的研究员Steven Shiau说,我们应该有个专门做灾难恢复、磁盘克隆的Linux工具盘,于是有了Clonezilla。(2004)
  4. 中国开发者冷罡华和刘文欢说,中文的Linux还可以做的更好,于是有了Hiweed,也就是现在的Deepin。(2004)
  5. CERN说,费米搞了个发行版,看来我们也需要一个,于是有了CERN Linux。(2004)
  6. 费米实验室和CERN说,既然双方都在搞Linux发行版,那能不能合作一下?于是有了Scientific Linux。(2004)
  7. Canonical说,我们需要让KDE爱好者也能用Ubuntu,于是有了Kubuntu。(2005)
  8. Canonical说,一个瘦客户端架构并预装了教学软件的Ubuntu会在学校里更受欢迎,于是有了Edubuntu。(2005)
  9. 诺基亚说,用Linux应该能搞出不错的智能手机/平板的触屏操作系统,于是有了OS2005,也就是后来的Maemo。(2005)
  10. 法国安全工程师Jean-Philippe Guillemin说,我们需要一个专门针对互联网应用、多媒体和编程人员的发行版,于是有了Zenwalk。(2005)
  11. 来自法国的软件工程师Clement Lefebvre说,Ubuntu还可以更好用,更漂亮,具备更多的辅助功能,做到更多国家的本地化,于是有了Linux Mint。(2006)
  12. 一群Ubuntu用户们说,我们应该有个基于Xfce桌面的Ubuntu,于是有了Xubuntu。(2006)
  13. Novell说,把SUSE桌面版交给社区吧,于是有了openSUSE。(2006)
  14. 红旗说,把红旗桌面版交给社区吧,于是有了Everest,也就是现在的Qomo。(2006)
  15. 甲骨文说,我们需要自己的Linux产品线,于是有了Oracle Enterprise Linux。(2006)
  16. 一群Ubuntu爱好者说,我就要一个只装了MythTV的Ubuntu做家庭影院,于是有了Mythbuntu。(2007)
  17. 英特尔说,Atom处理器在移动和上网本领域有点不给力啊,需要一些强力的OS协助推动,于是有了Moblin。(2007)

云计算时代:2008 –

  1. Damn Small Linux的开发者Robert Shingledecker说,其实系统还可以更小,我们把一个应用浏览器GUI加载到RAM中运行其实就可以满足很多用户的需求了,于是有了Tiny Core Linux。(2008)
  2. Google说,其实操作系统有Chrome就够了,于是有了Chromium OS。(2009)
  3. 法国创业者Tariq Krim和Romain Huet说,把常用的什么社交网络、在线视频照片网站的图标放在桌面上当做Web应用就挺好的,于是有了Jolicloud。(2010)
  4. 英特尔说,设备这种事还是需要懂行的来做,诺基亚你来跟我一起干吧,于是有了MeeGo。只是,后来AMD也掺和了进来,而诺基亚却走了,这是后话。(2010)

自从10年回了湖南,就没怎么在网上活动过了,以前的所有网站、博客等等的都已经不存在了,最主要是这段时间在进入一个新的行业,而这个行业也很少会涉及到互联网的事儿。

现在最主要还是把自己的工作做好咯,毕竟对于我来说是一个全新的行业,但是我也不会把自己的老本行和兴趣给全然抛弃了,这里最主要还是记录一下我的每一次进步,就作为自己的学习与开发日志吧。

从PHP转到Java,再转到Python,这么多年,现在也终于确定自己的最终定位了,C++和Python两个语言,这两个我是铁定了要会了,而且是要精通的,Java是绝对彻底放弃了的,另外,PHP因为很多原因,我是不会抛掉的,但也不会更深入的去研究了,只要自己够用就行。

Linux还是很不错的,我现在用的是Linode的VPS,使用的是CentOS 6,Nginx作为服务器软件,仅安装了PHP的支持,另外后期的一些项目也都会放在这个服务器上面,项目的主要开发语言将是Python,可能会使用到SQLAlchemy,Flask等框架,毕竟从头开发,不实际……

我的服务器其实安装也没有多少是自己一个一个配置的,而是使用了方便的LNMP快捷安装包,要是你和我一样,一般的懒的话,也建议你去LNMP官方网站下载,安装十分简单,具体流程网站上面有介绍,另外,那里还有一个LNMPA升级包,安装的是Nginx和Apache,不过一般不需要,尤其像我等这种小站。