Archive for September, 2007

在基于Pylons的服务器上测试使用DBUtils前后的性能对比

Sunday, September 30th, 2007

在基于Pylons的服务器上测试使用DBUtils前后的性能

目录

为了测试使用DBUtils实现的数据库连接池的性能,在Pylons写了一个测试服务,并将服务器架设在lab2服务器上,使用apache ab进行服务器压力测试。

1   测试环境

lab2服务器,MySQL 4.1.20,Pylons 0.9.4.1,apache ab 2.0.59。

为了确保测试的可靠性,每次测试之前都重启服务器。

在Pylons上假设的应用有3个URL用于测试,分别如下:

URL 说明
/testdb/test1 不使用连接池,每次访问都建立对数据库的连接
/testdb/test2 使用DBUtils.PersistentDB连接池,线程专用连接
/testdb/test3 使用DBUtils.PooledDB连接池,线程间共享连接

测试代码如下:

from helloworld.lib.base import *
import time
import random
import MySQLdb
import DBUtils.PersistentDB
import DBUtils.PooledDB

conn_kwargs={'host':'192.168.1.239','user':'ro','passwd':'','db':'test','port':3306}
sql="""SELECT * FROM test_table WHERE id=%d"""
persist=DBUtils.PersistentDB.PersistentDB(dbapi=MySQLdb,maxusage=1000,**conn_kwargs)
pooled=DBUtils.PooledDB.PooledDB(dbapi=MySQLdb,maxusage=1000,**conn_kwargs)

def query(conn):
    cur=conn.cursor()
    cur.execute(sql%(random.randint(1,1000)))
    data=cur.fetchall()
    cur.close()
    return data

class TestdbController(BaseController):
    def index(self):
        return Response('index')

    def test1(self):
        conn=MySQLdb.connect(**conn_kwargs)
        data=query(conn)
        conn.close()
        return Response(str(data))

    def test2(self):
        conn=persist.connection()
        data=query(conn)
        conn.close()
        return Response(str(data))

    def test3(self):
        conn=pooled.connection()
        data=query(conn)
        conn.close()
        return Response(str(data))

2   10线程并发

一共10000次测试,测试所用命令如下:

./ab -n 10000 -c 10 http://192.168.1.239:5000/testdb/test*

测试结果如下:

测试目标 总时间 请求处理速度 平均处理时间 错误率 100% 99% 90% 50%
/test1 32.764 305.22 32.764 ms 10.32% 776 237 40 29
/test2 27.895 358.49 27.895 ms 10.00% 3032 222 31 22
/test3 29.513 338.83 29.513 ms 10.46% 3037 58 36 27

3   50线程并发

一共10000次测试,测试所用命令如下:

./ab -n 10000 -c 50 http://192.168.1.239:5000/testdb/test*

测试结果如下:

测试目标 总时间 请求处理速度 平均处理时间 错误率 100% 99% 90% 50%
/test1 32.786 305.00 163.932 ms 9.48% 21031 3048 49 31
/test2 27.884 358.62 139.424 ms 9.65% 9227 3032 33 22
/test3 29.256 341.81 146.281 ms 9.88% 3654 328 151 136

4   远程10线程并发

一共10000次测试,测试所用命令如下:

./ab -n 10000 -c 10 http://192.168.1.241:5000/testdb/test*

测试结果如下:

测试目标 总时间 请求处理速度 平均处理时间 错误率 100% 99% 90% 50%
/test1 24.891 401.75 24.891 ms 9.07% 3035 44 31 22
/test2 21.652 461.85 21.652 ms 9.86% 256 59 26 19
/test3 23.952 432.99 23.095 ms 9.59% 239 38 28 22

5   远程50线程并发

一共10000次测试,测试命令如下:

./ab -n 10000 -c 50 http://192.168.1.241:5000/testdb/test*

测试结果如下:

测试目标 总时间 请求处理速度 平均处理时间 错误率 100% 99% 90% 50%
/test1 24.915 401.36 124.575 ms 9.82% 9280 3033 53 27
/test2 21.521 464.66 107.607 ms 9.47% 9621 3022 32 20
/test3 22.808 438.45 114.038 ms 9.11% 9107 145 114 95

6   干扰因素

测试过程中发现,MySQL服务器的同时并发连接数一直没有超过10,所以在进行50线程并发操作时可能会出现一些干扰。

7   单线程测试

使用代码如下:

import time
import random
import MySQLdb
import DBUtils.PersistentDB
import DBUtils.PooledDB

conn_kwargs={'host':'192.168.1.239','user':'ro','passwd':'','db':'test','port':3306}
sql="""SELECT * FROM test_table WHERE id=%d"""
persist=DBUtils.PersistentDB.PersistentDB(dbapi=MySQLdb,maxusage=1000,**conn_kwargs)
pooled=DBUtils.PooledDB.PooledDB(dbapi=MySQLdb,maxusage=1000,**conn_kwargs)

def query(conn):
    cur=conn.cursor()
    cur.execute(sql%(random.randint(1,1000)))
    data=cur.fetchall()
    cur.close()
    return data

def print_now():
    print time.strftime("%H:%M:%S")
    return

def test1(times):
    print_now()
    for i in range(0,times):
        conn=MySQLdb.connect(**conn_kwargs)
        query(conn)
        conn.close()
    print_now()
    return

def test2(times):
    print_now()
    for i in range(0,times):
        conn=persist.connection()
        query(conn)
        conn.close()
    print_now()
    return

def test3(times):
    print_now()
    for i in range(0,times):
        conn=pooled.connection()
        query(conn)
        conn.close()
    print_now()
    return

8   单线程测试

执行10000次查询,进入Python交互模式,调用各个函数并传递执行次数,每次执行过后重启MySQL服务器:

# python -i ttss.py
>>> test1(10000)
18:59:30
18:59:40
>>> test2(10000)
19:00:16
19:00:19
>>> test3(10000)
19:00:46
19:00:49

可见查询次数太少,以致难以精确测定时间,所以执行100000次查询,过程如下:

# python -i ttss.py
>>> test1(100000)
19:01:57
_mysql_exceptions.OperationalError: (2003, "Can't connect to MySQL server on '192.168.1.239' (99)")

连续两次都出现异常,之后改为30000也是如此。出现这个异常之后数据库服务器不经过重启就无法再使用了。经过测试发生这种连接异常之后,还是可以使用mysql客户端登录本机的MySQL服务器的。所以改为20000次查询,过程如下:

# python -i ttss.py
>>> test1(20000)
19:06:47
19:07:07
>>> test2(20000)
19:28:23
19:28:28
>>> test3(20000)
19:29:27
19:29:34

测试远程连接MySQL服务器:

# python -i ttss.py
>>> test1(10000)
20:25:23
20:25:57
>>> test2(10000)
20:27:18
20:27:26
>>> test3(10000)
20:27:46
20:27:56

9   结论

总体上来看,使用了DBUtils之后数据库的访问性能有了很大的提高。

Python并行编程之 Parallel Python文档页

Sunday, September 30th, 2007

Parallel Python文档页

原文: http://www.parallelpython.com/content/view/15/30/

目录

1   PP 1.4.2 模块API

class

Server

class Server

并行Python SMP执行服务器类

方法定义:

__del__(self)

__init__(self,ncpus=’autodetect’,ppservers=(),secret=None,loglevel=30,logstream=<open file ‘<stderr>’,mode ‘w’>)

创建服务器实例

ncpus – 本机要启动的工作进程数量,如果省略则为处理器数量

ppservers – 可用的PP服务器

secret – 网络连接密码,省略则使用缺省密码,简易在所有连接中使用一个自定义密码

loglevel – logging模块的日志级别

logstream – 日志目标

使用ncpus=1则所有任务串行执行。要获取最好性能则使用’autodetect’值,或手动设置CPU数量。

get_ncpus(self)

获取工作进程数量

get_stats(self)

返回所有工作执行状态字典

print_stats(self)

打印工作执行状态,用于集群测试标准。

set_ncpus(self,ncpus=’autodetect’)

设置工作进程数量

ncpus – 工作进程数量,如果省略则设置为系统处理器数量

submit(self,func,args=(),depfuncs=(),modules=(),callback=None,callbackargs=(),group=’default’,globals=None)

提交函数到执行队列

func – 需要执行的函数

args – func函数的执行参数tuple

depfuncs – 待执行函数需要调用的函数tuple

modules – 需要被import导入的模块名字tuple

callback – 在函数执行完成之后的回调函数,参数为callbackargs+(result,)

callbackargs – 附加的回调函数参数

group – 工作组,用于当 wait(group) 被调用,等待工作组全部工作结束时

globals – 字典,所有模块和函数共享与导入的,例如 globals=globals()

wait(self,group=None)

等待工作组中所有工作结束,如果忽略group参数则等待所有工作结束

数据和其他属性:

default_port = 6000

default_secret = ‘epo20pdosl;dksldkmm’

Data

copyright = ‘Copyright (c) 2005-2007 Vitalii Vanovschi. All rights reserved’

version = ‘1.4.2’

2   快速开始,SMP

  1. 导入pp模块:

    import pp
  2. 启动PP执行服务器,按照处理器数量指定工作进程数:

    job_server=pp.Server()
  3. 提交需要进行并行计算的任务:

    f1=job_server.submit(func1,args1,depfuncs1,modules1)
    f2=job_server.submit(func2,args2,depfuncs2,modules2)
    f3=job_server.submit(func3,args3,depfuncs3,modules3)
    # ...etc...
  4. 取回结果:

    r1=f1()
    r2=f2()
    r3=f3()
    # ...etc...

查找如何使任务并行化,参考例子 例子

3   快速开始,集群

  1. 在各个计算节点上启动并行Python执行服务器:

    node-1> ./ppserver.py
    node-2> ./ppserver.py
    node-3> ./ppserver.py
  2. 导入pp模块:

    import pp
  3. 创建集群中计算节点列表(就是运行ppserver.py的机器):

    ppservers=("node-1","node-2","node-3")
  4. 使用计算节点列表和其他参数来启动执行服务器:

    job_server=pp.Server(ppservers=ppservers)
  5. 提交并行执行任务,同SMP的例子

  6. 取回结果,同SMP的例子

4   高级指南,集群

  1. 在各个计算节点上启动并行Python计算服务器,监听本地35000端口,仅接受密码正确的连接:

    node-1> ./ppserver.py -p 35000 -i 192.168.0.101 -s "mysecret"
    node-2> ./ppserver.py -p 35000 -i 192.168.0.102 -s "mysecret"
    node-3> ./ppserver.py -p 35000 -i 192.168.0.103 -s "mysecret"
  2. 导入pp模块,同SMP例子

  3. 创建集群中计算节点列表,就是运行ppserver.py的机器:

    ppservers=("node-1:35000","node-2:35000","node-3:35000")
  4. 启动pp执行服务器,同时指定集群列表和验证密码:

    job_server=pp.Server(ppservers=ppservers,secret="mysecret")
  5. 提交并行计算任务,同SMP例子

  6. 取回结果,同SMP例子

  7. 显示执行状态:

    job_server.print_stats()

5   命令行参数,ppserver.py

Usage: ppserver.py [-hd] [-i interface] [-p port] [-w nworkers] [-s secret]
Options:
-h                 : 显示这个帮助信息
-d                 : 调试
-i interface       : 监听所用的网口
-p port            : 监听端口
-w nworkers        : 工作进程数量
-s secret          : 认证密码

Python并行编程之 Parallel Python首页

Sunday, September 30th, 2007

Parallel Python首页

原文: http://www.parallelpython.com/

目录

1   简介

PP 是一个Python模块,提供了在SMP(多CPU或多核)和集群(通过网络连接的多台计算机)上并行执行Python代码的机制。轻量级,易于安装,并集成了其他软件。PP也是一个用纯Python代码实现的跨平台,开放源码模块。

2   功能

  • 在SMP和集群上并行执行Python代码
  • 易于理解和实现的基于工作的并行机制,便于把穿行应用转换成并行的
  • 自动构造最佳配置(默认时工作进程数量等同于系统处理器数量)
  • 动态处理器分配(允许运行时改变工作处理器数量)
  • 函数的工作缓存(透明的缓存机制确保后续调用降低负载)
  • 动态负载均衡(任务被动态的分配到各个处理器上)
  • 基于SHA的连接加密认证
  • 跨平台移植(Windows/Linux/Unix)
  • 开放源代码

3   开发动机

现代Python程序已经广泛的应用在商业逻辑,数据分析和科学计算等方面。其中广泛应用着SMP(多处理器或多核)和集群(通过网络连接的多台计算机),市场需要并行的执行的Python代码。

在SMP计算机上编写并行程序最简单的方法是使用多线程。尽管如此,使用 ‘thread’ 和 ‘threading’ 模块仍然无法在字节码一级实现并行。因为Python解释器使用GIL(全局解释器锁)来在内部禁止并行执行。这个GIL限制你在SMP机器上同一时间也只能执行一条字节码指令。

PP 模块正是为了解决这个问题而来,提供简单的方式实现并行Python应用。 ppsmp 在内部使用 进程IPC (进程间通信)来组织并行计算。并处理了所有内部的细节和复杂性,你的应用程序只需要提交工作任务并取回结果就可以了。这也是编写并行程序的最简单的方法。

为了更好的实现,所有使用 PP 的软件通过网络来连接和协作。跨平台和动态负载均衡使得 PP 可以轻松组织多平台、异构的集群计算环境。

4   安装

任何平台:下子阿模块压缩包,解压,运行setup脚本:

python setup.py install

Windows:下载和执行安装包。

5   文档

模块API

快速开始,SMP

快速开始,集群

高级指南,集群

命令行参数,ppserver.py

6   例子

并行Python的使用方法

7   下载

并行Python下载

8   技术支持

并行Python讨论区 提供帮助和支持。

9   帮助我们扩展,链接我们

<a href='http://www.parallelpython.com'>Parallel Python</a>

Twisted的WEB开发

Sunday, September 30th, 2007

Twisted的WEB开发

作者: gashero <harry.python@gmail.com>

目录

1   简介

在WEB开发中,偶尔需要对HTTP协议更多底层细节进行控制,这时的django/web.py等等显然无法满足要求,所以只好求助于Twisted了。使用Twisted进行WEB开发,其实更合适的叫法应该是基于HTTP服务器的开发,因为Twisted相对底层,所以可以控制的东西也比较底层。

在Twisted的技术体系中,这个WEB开发实际上要涉及到HTTPChannel、HTTPFactory、Request三个层次的开发,以下详诉。

HTTP协议参考 RFC2616

2   Twisted技术体系

Twisted技术体系包含2个层次:协议和工厂。协议负责连接成功以后对交互的处理,而工厂则是负责连接过程。在HTTP协议中,连接之后还有个生成HTTP请求报文的过程,所以构造出了一个Request对象来处理具体的一个HTTP请求的报文。

在HTTP中的请求报文处理对象是 twisted.web.http.Request 类;HTTP的协议类是 twisted.web.http.HTTPChannel ;HTTP工厂是 twisted.web.http.HTTPFactory 。

3   一个简单的例子

节选自《Twisted网络编程必备》:

from twisted.web import http

class MyRequestHandler(http.Request):
    pages={
        '/':'<h1>Home</h1>Home Page',
        '/test':'<h1>Test</h1>Test Page',
        }
    def process(self):
        if self.pages.has_key(self.path):
            self.write(self.pages[self.path])
        else:
            self.setResponseCode(http.NOT_FOUND)
            self.write("<h1>Not Found</h1>Sorry, no such page.")
        self.finish()

class MyHttp(http.HTTPChannel):
    requestFactory=MyRequestHandler

class MyHttpFactory(http.HTTPFactory):
    protocol=MyHttp

if __name__=="__main__":
    from twisted.internet import reactor
    reactor.listenTCP(8000,MyHttpFactory())
    reactor.run()

与其他很多框架不同,TwistedWEB只有一个核心的请求处理类Request,各个针对不同的URL的请求也要通过这里来分发。而这个类只要重载 process() 方法就可以了,期间的很多数据都可以通过self来引用。

请求的处理流程也就是判断对不同URL的不同处理,然后向客户端写入响应信息,并在最后调用关闭请求。步骤如下:

  1. 过滤URL, self.path
  2. self.write(data) 向客户端写入数据
  3. self.finish() 关闭响应

4   Twisted WEB Request参考

来自分析 twisted.web.http.http.py 源代码。

4.1   请求

包含请求的数据,这里都是指Request类的成员。

channel :包含上级的HTTP协议对象。

transport :通信对象。

method :HTTP方法,如GET和POST。

uri :全部请求的URI。

path :具体的请求路径,不含参数。

args :请求参数,包括URL参数和POST参数。格式如 {'key':['val1','val2'],}

received_headers :请求报文的头字段。

received_cookies :请求报文的cookie。

content :请求报文的实体主体,文件对象。

clientproto :发出请求的客户端的HTTP版本。

client :?

host :?

getHeader(key) :获取请求的头字段。

getCookie(key) :获取请求的cookie。

getAllHeaders() :所有请求的头字段字典,就是返回received_headers。

getRequestHostname() :请求的host字段,不含端口号。

getHost() :原始请求的通信地址,返回host。

getClientIP() :获取客户端IP。

getUser() :获取basic验证中的用户名。

getPassword() :获取basic验证中的密码。

getClient() :?

4.2   响应

包含响应的数据,这里都是Request类的成员。

headers :字典,包含响应报文的头字段。

cookies :字典,包含响应报文的cookie。

finish() :结束响应报文。

write(data) :向客户端发送数据,经过了HTTP包装了。

addCookie(k,v,expires=None,domain=None,path=None,max_age=None,comment=None,secure=None) :为响应报文添加一个cookie。

setResponseCode(code,message=None) :设置响应代码,code参考常量定义。

setHeader(k,v) :设置头字段。

redirect(url) :HTTP重定向。

setLastModified(when) :设置缓存超时,when的值为长整型的那个时间。

setETag(etag) :设置缓存标志,用于在内容更改时让用户有所发觉。

setHost(host,port,ssl=0) :设置请求地址。用于代理服务器的重定向。

4.3   常量

没有响应主体的code:

NO_BODY_CODES=(204,304)

responses=RESPONSES :字典,保存了各个响应码的对应提示信息。

响应报文中的响应码:

OK=200 :请求处理成功,最常见的响应代码,但是正因为常见,所以默认就是这个了,也无须设置到setResponseCode。

NOT_MODIFIED=304 :请求的资源没有没有修改过,用于浏览器缓存。

BAD_REQUEST=400 :请求报文有语法错误。

UNAUTHORIZED=401 :尚未认证,要求用户输入认证信息。

FORBIDDEN=403 :禁止访问。

NOT_FOUND=404 :请求的资源不存在。

INTERNAL_SERVER_ERROR=500 :服务器内部错误。

NOT_IMPLEMENTED=501 :该功能尚未实现。

BAD_GATEWAY=502 :请求路径错误。

4.4   HTTPChannel

构造函数无参数,处理HTTP报文。

requestFactory=Request :指定了请求报文处理工厂。

4.5   HTTPFactory

__ini__(logPath=None,timeout=60*60*12) :构造函数可以设置日志和超时。

buildProtocol(addr) :内部的构造协议对象的方法,不要调用。

protocol=HTTPChannel :设置协议对象。

5   比较完善的开发模式

建立一个Request类的子类作为请求工厂,或者说请求发布器,其中有识别不同的URL并的能力,通过字典找到该URL对应的函数,调用这个函数并传递self参数。每个具体的请求处理函数也只有1个request参数,返回数据都是直接写入request.write()中。

一般来说请求工厂的process()中需要设置响应类型,如网页的:

self.setHeader("Content-Type","text/html; charset=GB2312")

同时也要在没有对应的URL时告知客户端找不到:

self.setResponseCode(http.NOT_FOUND)
self.write("<h1>Not Found</h1>Sorry, no such page.")
self.finish()

至于self.finish()放在各个请求处理函数中还是放在process(),就是个人爱好问题了。比较推荐放在process()中。

提取请求参数的重点在request.args字典,每个键都是映射到一个列表,为了适应HTTP提交中一个键对应多个值的情况,当然,你也可以只取第一个值。

6   以resource方式提供WEB资源

  1. 每个资源都是 twisted.web.resource.Resource 的子类
  2. 可以自己定义构造函数
  3. 要重载 render(self,request) 方法来响应请求
  4. render 方法中的request对象实际就是Request的实例

一个例子:

from twisted.web import resource,static,server

class HomePage(resource.Resource):

    def render(self,request):
        request.write("Home Page")
        return

    def getChild(self,path,request):
        return AnotherPage() #另外一个Resource的子类

if __name__=="__main__":
    from twisted.internet import reactor
    root=resource.Resource()
    root.putChild('',HomePage())
    root.putChild('color',ColorRoot())
    root.putChild('style.css',static.File('style.css'))
    site=server.Site(root)
    reactor.listenTCP(8000,site)
    reactor.run()

可以通过各个Resource的构造参数传入path参数,用以后来寻找下级Resource的参数。

Note

关于Resource还有很多细节,但是对本文意义不大,所以略。

7   总结

总的来说,用Twisted来开发更适合于开发个框架,而不是直接做WEB应用,有如一直都很少有人直接用 mod_python 来开发WEB应用一样。

函数式编程-考验人品

Friday, September 28th, 2007

函数式编程-考验人品

好友jorge(附加形容词:牛人,超牛,巨牛无比,变态,下流)推荐我学函数式编程语言scheme,以前倒是考虑过erlang,不过实在没空所以暂时没有动手。不过Python中对函数式编程倒是有支持,所以最近尝试了一下。

问题如下,一个字符串形式的IP地址,比如”12.24.0.9″,需要转换成32bit无符号整数形式表示的数字。这个其实可以用socket.inet_aton来实现,但是问题是,我拿到的这些IP地址都是这个形式”012.024.000.009″,来自珊瑚虫IP数据库的导出结果,而inet_aton会把这些一段段的以0开头的数字识别为八进制,所以,只能自己写转换函数。

最终呢,就是用函数式编程来解决了问题,实际代码只有一行。代码如下:

ipstr=”012.024.000.009″
numset=reduce(lambda x,y:x*256+y,map(lambda numlist:reduce(lambda x,y:x*10+y,numlist),map(lambda strlist:map(int,strlist),map(list,list(ipstr.split(‘.’,3))))))
from socket import inet_ntoa
import struct
print inet_ntoa(struct.pack(‘!L’,numset))

可以看出函数式编程的代码可读性确实河蟹的可以,我午饭前写的,现在就已经基本看不懂了。不过在TDD(测试驱动开发)的指导下,所有测试用例通过了,就算过了。

函数式编程看起来确实很酷,写这么长的语句一看就是人品很河蟹,所以有如国外的牛说的那样,他们一直希望取缔Python中的函数式编程支持。读者么如果有希望考验自己人品的,也可以多用用函数式编程。呵呵,先这样吧。

Pytho WEB 编程的学习方法

Wednesday, September 12th, 2007

Pytho WEB 编程的学习方法

日期: 2007-09-12

目录

Python是一种开发效率较高的语言,因此比较适合需求快速变化并且要求快速开发的WEB开发领域。在此对Python的WEB开发做一些简要介绍并对已有的PythonWEB编程技术的学习方法进行一些分析。

1   Python WEB开发现状

Python的快速开发能力使得其在WEB开发中应用广泛,因此也诞生了很多Python的WEB开发框架。Python的WEB开发框架较多一方面依赖于Python的方便性,另一方面也导致了入门时选择框架的麻烦。先进比较流行的几种Python WEB开发框架有Django、TurboGears、Zope等。

同时,由于很多Python WEB开发框架都是基于MTV模式(区别于MVC),所以也同时产生了很多种模板系统,比较流行的有Cheetah等几种。

为了快速的实现数据库的定义与关联,也产生了几种数据库操作框架,流行的几种包括SQLObject、SQLAlchemy、DBUtils等等。

同时又有一些WEB框架本身使用自己专用的模板系统和ORM系统,比如django、Zope等。

虽然面对林林总总的这么多选择,让人总是不知所措,但是实际上各类框架之间有很多相似之处,也存在很多重复实现。只要学通了其中一套框架体系(包含WEB框架、模板和ORM),其他的框架体系也就都可以触类旁通了。

另外,现在的Python WEB开发也正在逐渐向着以django为主的集中靠拢,所以,本文郑重推荐初学者学习django。

2   WEB开发基础-HTTP

2.1   GET请求

撰写中……

2.2   POST表单

撰写中……

3   WEB开发的基本工具

3.1   重定向

撰写中……

3.2   内部重定向

撰写中……

3.3   cookie

撰写中……

3.4   session

撰写中……

4   几种Python WEB开发框架的对比

这里仅对比如下几种比较流行的WEB开发框架,不求全,但求对比。数据取自2007年9月12日。

框架/功能 django TurboGears Zope CherryPy web.py Twisted mod_python
开始时间 2005.7 2005.9 199x.x 2004.11 2006.1 2001.7 2000.10
书籍数量 1 2 N 1 0 1 1+
单一/组合 单一 组合 单一 不完善 单一 不完善 单一
开发难度 一般 一般 较难 简单 超简单 较难 一般
google搜索结果 2.6M 2.12M 2.48M 1.53M 0.78M 2.11M 2.49M
google中文结果 0.68M 0.12M 0.63M 58.4K 25.7K 0.31M 78.6K

4.1   django

比较推荐,详见下面介绍。

4.2   TurboGears

一套组合框架,HTTP处理使用cherrypy,ORM可以使用SQLObject或SQLAlchemy,模板系统默认为kid,可以更换。作为一套组合框架,有着很多个不同的组件。从TG的哲学上讲这叫不重新发明轮子。但是从我Pylons的经验来看,使得各个组件可以和谐的工作在一起也是很困难的。至少我就曾经因为差不多的Pylons使用的某个组件更新版本的差异最终放弃了Pylons。

TurboGears的优点很明显,有大部头的书,文档丰富。另外,就是其版本已经到了1.0.3了,稳定性值得信赖,另外对于开源软家来说,1.0以上的版本意味着接口的稳定。从表格中可以看出,国内用TG的人很少。我最初对TG的好感也仅限于用过CherryPy一段时间。

4.3   Zope

Python中的巨无霸WEB开发框架,曾经是绝对的选择,后来么,因为学习成本的问题而使用略少了一些。对于初学者不太推荐,因为稍微重量级了一点,可以对比J2EE。至于稳定性,据说很多国外政府和军队的网站在用。而且基于Zope开发的Plone是世界排名第三位的CMS系统。文档么,有中文的书籍支持,英文的更不必说。当然,Zope也是一套组合的框架,一些在Python WEB开发方面的机制当初都是Zope开创的,另外在面向对象数据库等方面,Zope也拥有领先的优势。

如果将来要做Python的企业级WEB开发,绝对推荐。

4.4   CherryPy

一套比较简单的WEB开发框架,只含有WEB开发框架。开发很简单,是并不算是方便,因为ORM和模板等等需要自己来选择和配置。比较适合开发实验系统或为其他应用提供一个WEB管理界面。

如果需要学习TurboGears也推荐先从CherryPy学起,这样很多内部机制就好理解了。

4.5   web.py

一套非常简单的框架,不过并不是非常简洁。只提供WEB开发框架而没有其他东西,但是内部做了对流行的ORM和模板系统的导入引用。使用方式在WEB框架里面是比较独特的,只需要把web.py这一个文件放到工程目录里面带走即可,到其他机器上运行时也无需安装,直接可以使用。因此更加适合做其他系统的WEB管理界面。

如果有兴趣推荐读一下web.py的代码,里面有很多先进的思想对未来的编程很有帮助。

4.6   Twisted

Twisted是一套巨无霸的网络编程框架,其WEB开发不过是HTTP协议实现的一个附带功能,如果希望了解足够深的底层知识并控制这些底层细节,那么推荐。因为连HTTP协议的某些细节你都要照顾到。

所以呢,如果你希望提供一些超高性能的WEB接口,比如AJAX应用,XMLRPC服务器等等,那么强烈推荐,因为这个东东真的是太快了。在我的测试中AMD64 x2的机器用apache ab测试,100并发时每秒可以处理1500个HTTP请求。这速度已经没得挑了。

4.7   mod_python

apache-httpd服务器的Python解释器插件,允许直接控制apache服务器来提供WEB功能。速度比较快,但是速度不是强项。强项是稳定性,拖apache的福,在多进程中使用的mod_python稳定性巨牛无比,即便是死掉一个子进程对整体服务器也没什么影响。但是mod_python一般作为如上多种WEB开发框架的Apache-httpd的API接口来使用,提供高可靠性的服务器前端。至于直接用mod_python编程呢,提供了PSP、publisher、自定义模块等几种方式。总体来说也不难,但是大家习惯了把它当底层了,所以也就没人愿意学了。

文档方面,虽然google中文结果很少,但是却又两份官方文档翻译,这在这些框架里面文档的权威性仅次于Zope。反倒是其他框架的中文资料里面hello world满天飞。国内现在仅有的两家提供Python虚拟主机的,有一家就是专门做mod_python的,当然,另外一家专门做django。

5   几种模板系统的对比

撰写中……

6   几种ORM对比

撰写中……

7   Python WEB 快速入门

有如上面所说的,Python的WEB编程,各个框架之间相似度很大,可以按照一定的步骤了解几个重要的知识点,就可以迅速的过渡到另一个框架了。推荐步骤如下:

  1. 开发服务器的启动和端口配置
  2. hello world程序
  3. 返回字符串的方法
  4. 调用模板的方法(对单一框架则没有)
  5. URL组织
  6. 重定向
  7. 提交参数处理
  8. cookie & session
  9. 调用ORM的方法(对单一框架则没有)
  10. 附加功能,如表单生成与自动验证(TurboGears、Django)、通用模板(django)、中间件支持(Pylons…)、内置认证、AJAX支持等等
  11. 服务器部署

8   django

撰写中……

在Python独立访问django数据模型

Tuesday, September 11th, 2007

在Python独立访问django数据模型

在需要使用其他独立运行的程序调用django的数据模型时往往会提示导入数据模型时的EnvironmentError异常。其实这都是因为没有正确的找到django的配置文件所造成的。这其实也难怪django,连数据库的连接信息都在settings.py中,没有指定这个文件当然没法使用模型了。

好了谈谈解决方法吧。按照django官方手册上的讲解,就是如下:

$ ./manage.py shell

这样就可以直接进入Python交互模式了,并且可以访问很多东东。

但是问题时,有时候需要使用其他独立运行的Python脚本来访问数据模型,也就不是在交互模式下。这时可以自己设置环境变量DJANGO_SETTINGS_MODULE来达到同样的效果。

对于交互模式可以这样运行:

$ DJANGO_SETTINGS_MODULE=settings python

这样的解释器就可以直接使用其他环境变量了。

也可以先设置好环境变量,然后再使用Python,不过这时如果系统当中有多个django工程时就比较麻烦了,总之自己考量吧。方法如下:

$ export -p DJANGO_SETTINGS_MODULE=settings
$ python

另外,有如前面说的,在有多个工程时,如果需要启动另外一个,则可以清除环境变量,如下:

$ export -n DJANGO_SETTINGS_MODULE

这样就又恢复到原来的样子了。

祝大家django愉快。

gashero的Pylons笔记

Tuesday, September 11th, 2007

gashero的Pylons笔记

目录

1   《Pylons初探》笔记

1.1   安装

  1. 下载安装工具ez_setup.py

  2. 命令行运行如下:

    python ez_setup.py Pylons
  3. 设置环境变量

    pyth -> C:Python24Scripts

  4. 运行命令paster,如果运行成功会出现如下结果:

    Usage: C:\Python24\Scripts\paster-script.py COMMAND
    ... ...

1.2   开始动手

  1. 新建一个Pylons工程:

    paster create --template=pylons helloworld

    运行成功后产生如下结果:

    Selected and implied templates::
    pylons#pylons  Pylons application template
    ... ...
  2. 运行这个新建的工程

    1. 启动服务器:

      cd helloworld
      paster serve --reload development.ini
    2. 访问 http://127.0.0.1:5000/ 将会看到欢迎页面

    3. helloworld/public 目录下创建test.html文件,内容如下:

      <html>
          <body>
              Hello world!
          </body>
      </html>
    4. 访问 http://127.0.0.1:5000/test.html 看到结果。

  3. 禁用调试功能

    将development.ini文件中的如下行的注释删除:

    # set debug=false
  4. 创建一个控制器,修改Routes

    1. 命令行运行:

      paster controller hello
    2. 修改 helloworld/controllers/hello.py ,代码如下:

      from helloworld.lib.base import *
      
      class HelloController(BaseController):
          def index(self):
              return Response('hello world')
    3. 修改 helloworld/config/routing.py ,代码如下:

      """
      Setup your Routes options here
      """
      import sys,os
      from routes import Mapper
      
      def make_map(global_conf={}, app_conf={}):
          root_path=os.path.dirname(os.path.dirname(\
                  os.path.abspath(__file__)))
          map=Mapper(directory=os.path.join(root_path,'controllers'))
          # 这个route handle用来显示错误页面
          map.connect('error/:action/:id', controller='error')
          #定义自己的route,可以定义很多细节
          #更多有用的信息参考http://routes.groovie.org/docs/
          map.connect('',controller='hello',action='index')
          map.connect(':controller/:action/:id')
          map.connect('*url',controller='template',action='view')
          return map
    4. 删除 public/index.html 。访问 http://127.0.0.1:5000/hellohttp://127.0.0.1:5000/ 将会看到 Hello world。

  5. 模板和请求周期

    1. 创建模板文件 helloworld/templates/serverinfo.myt ,代码如下:

      <p>Hi, here's the server environment: <br />
      <% str(request.environ) %></p>
      <p>
      and here's the URL you called: <% h.url_for() %>
      </p>
    2. 修改 helloworld/controllers/hello.py ,代码如下:

      from helloworld.lib.base import *
      class HelloController(BaseController):
          def index(self):
              return Response('hello world')
          def serverinfo(self):
              return render_response('/serverinfo.myt')
    3. 访问 http://127.0.0.1:5000/hello/serverinfo ,可以看到如下结果:

      Hi, here's the server environment:
      ... ...
      and here's the URL you called: /hello/serverinfo
  6. 使用Session,代码如下:

    def serverinfo(self):
        session['name']='George'
        session.save()
        return render_response('/serverinfo.myt')
  7. 控制器变量和模板全局变量

    控制器变量

    1. 修改 helloworld/controllers/hello.py ,代码如下:

      from helloworld.lib.base import *
      class HelloController(BaseController):
          def index(self):
              return Response('hello world')
          def serverinfo(self):
              c.value=2
              return render_response('/serverinfo.myt')
    2. 修改 helloworld/templates/serverinfo.myt ,代码如下:

      <p> The value of <tt>c.value</tt> is:
      <% c.value %>
    3. 访问 http://127.0.0.1:5000/hello/serverinfo ,可以看到 The value of c.value is: 2

    模板全局变量

    1. 修改 lib/app_globals.py ,代码如下:

      class Globals(object):
          def __init__(self,defaults,app,**extra):
              self.message='Hello'
          def __del__(self):
              """
              将清空代码放在这里
              """
              pass
    2. 修改 helloworld/controllers/hello.py ,代码如下:

      from helloworld.lib.base import *
      class HelloController(BaseController):
          def index(self):
              return Response('hello world')
          def serverinfo(self):
              c.value=2
              return render_response('/serverinfo.myt')
          def app_globals_test(self):
              resp=Response()
              if g.message=='Hello':
                  resp.write(g.message)
                  g.message='Hello world!'
              else:
                  resp.write(g.message)
              return resp
    3. 访问 http://127.0.0.1:5000/hello/app_globals_test/ ,可以看到 Hello world!

  8. 原文网址 http://pylonshq.com/docs/0.9.3/getting_started.html

2   Pylons首页信息

Pylons的最新版本为0.9.4.1,发布于2007-01-06。这是一个bugfix版本,原版本0.9.4发布于2006-12-29。上面那篇文章的对应版本为0.9.3,看来差的 还不是太远。

Pylons是一个轻量级的WEB开发框架,强调灵活性和快速开发。

2.1   为什么使用Pylons

Pylons结合了Ruby、Python和Perl的优点,提供了结构化的,但是却非常灵活的Python WEB开发框架。他也是首个支持新出现的WSGI标准的项目,在需要时允许广泛的重用和移植。Pylons的目标是使得WEB开发更加快速、灵活和简单。

更多信息

安装

开始学习

2.2   与其他组件一起使用

Pylons基于 Paste 并支持其他常见的Python组件:

2.3   最新的入门教程

2.4   有用的资源

你可能对 使用了Pylons的站点 感兴趣。如果你需要的信息在 文档 中是没有的,则可以参考 wiki ,这里的信息会有更快的更新,并且有更加全面的资源。当然,你也可以自己 添加文章

2.5   顶部导航栏链接

主页 文档 wiki 社区 FAQ 安装 Pastebin 查找文档

3   安装Pylons

首先,Windows上的Python2.3用户需要先安装 subprocess.exe (下载失效),而Python2.4用户则不需要。所有的Windows用户在安装完成后需要研读 安装后必读 ,而Ubuntu和Debian用户则需要安装 python-dev 包。

3.1   系统级安装

如果需要安装好的Pylons被所有用户所使用,就需要使用root帐户。如果你已经拥有了easy install,则可以执行如下命令:

$ easy_install Pylons

Note

偶尔python.org会挂掉。这时仍然可以用本地的文件来安装Pylons及其以来的包。:

easy_install -f http://pylonshq.com/download/ Pylons

这样会自动安装最新版本的包。如果你正在使用一个旧版本,则可以指定版本:

easy_install -f http://pylonshq.com/download/0.8.2/ Pylons

否则,先下载 easy install ,地址为 http://peak.telecommunity.com/dist/ez_setup.py 。然后运行:

$ python ez_setup.py Pylons

Note

你可以选择安装一个其他的扩展(extra),如下:

$ easy_install Pylons[extra]

pudge :支持构建构建文档,查看 为应用添加文档 。需要注意的是很多文档工具还处于开发中,并不是很稳定。

genshi :支持使用 genshi 模板,查看 使用其他模板语言

cheetah :支持使用 cheetah 模板,查看 使用其他模板语言

kid :支持使用 kid 模板,查看 使用其他模板语言

full :如上所有的

3.2   自定义安装位置

如果需要自定义安装位置,则可以参考:

http://peak.telecommunity.com/DevCenter/EasyInstall#custom-installation-locations

查看Easy Install的文档可以了解Pylons是否是安装为系统级。

http://peak.telecommunity.com/DevCenter/EasyInstall

3.3   升级安装

升级到最新版本使用:

$ easy_install -U Pylons

3.4   工作于前沿(Living on the Cutting Edge)

如果不满意于最新的发行版,或者希望使用源代码工作,可以使用最新的开发版本:

http://pylonshq.com/docs/0.9.4.1/pylonsdev.html

3.5   Easy Install 的问题

有时候如果Pylons依赖的包安装失败会导致Easy Install出错。这时候确保 setuptools 是最新版本,使用如下命令:

$ easy_install -U setuptools

然后再次尝试安装Pylons,如果再次安装失败,就先手动安装Pylons依赖的软件包,然后再安装Pylons。例如,如果你安装Myghty时,可以指定最新版本的位置:

$ easy_install -U -f http://pylonshq.com/download/ Myghty

或者你也可以直接使用URL上的发行版来安装。这时我们将会从cheeseshop安装Myghty 1.1,但是你必须确保安装当前Pylons版本所使用的文件:

$ easy_install -U http://cheeseshop.python.org/packages/source/M/Myghty/Myghty-1.1.tar.gz#md5=5865361811dca4054f1ec60ac32ee965

3.6   软件包注释

Pylons使用的 Cheetah、docutils、elementtree、nose等等软件包多多少少的被 James Gardner 修改过,所以可以被setuptools和Python2.3方便的安装和使用。这些软件包的某些安装指令需要手动调整。如果你不打算使用Pylons提供的这些软件包版本,Pylons开发组并不确保可以工作。

4   启动Pylons之后,主页上的例子

helloworld/public/ 目录在运行之前搜索需要显示的静态文件。删除文件 helloworld/public/index.html 然后修改路由(Route) helloworld/config/routing.py 成如下的样子:

map.connect('',controller='hello',action='index')

新建一个hello.py文件,内容如下:

# helloworld/controllers/hello.py
from helloworld.lib.base import *
class HelloController(BaseController):
    def index(self):
        return Response('Hello world')

然后访问 http://127.0.0.1:5000/hello 可以看到结果。

使用模板的例子,Myghty模板:

# helloworld/templates/serverinfo.myt
<p>Hi, here's the server environment: <br />
<% str(request.environ) %></p>
<p>here's the URL you called: <% h.url_for() %> </p>
<p>and here's the name you set: <% c.name %></p>

然后将这些添加到你的hello控制器类当中:

def serverinfo(self):
    c.name='The Black Knight'
    return render_response('/serverinfo.myt')

然后通过 http://127.0.0.1:5000/hello/serverinfo 来访问。

5   《Getting Started》比《Pylons初探》增加的内容

尽管《Pylons初探》就是翻译自《Getting Started》,但是版本并不相同。现在版本比原版本增加了一些内容。

http://pylonshq.com/docs/0.9.4.1/getting_started.html

5.1   简介

如果尚未完成安装,阅读 安装过程 。这个文档引导用户快速入门。

5.2   创建一个Pylons工程

Pylons使用Paste创建和部署工程,当然还可以创建控制器及其单元测试。

创建一个新工程叫做 helloworld 使用如下命令:

paster create --template=pylons helloworld

Note

Windows用户必须先配置 PATH 环境变量在 安装后必读 。至少应该可以在控制台中得到 paster 的路径。

这将会创建一个基本的工程目录,目录具有如下结构:

-helloworld
    -data
    -helloworld
    -helloworld.egg-info
        -Various files including paste_deploy_config.ini_tmpl
    -development.ini
    -setup.cfg
    -setup.py

文件 setup.py 用于创建自安装包,叫做 eggegg 可以看作等同于Java中的 .jar 文件。而 setup.cfg 包含了工程的其他信息,目录 helloworld.egg-info 包含关于egg的信息,包括一个 paste_deploy_config.ini_tmpl 文件用作工程使用的配置模板,可以用 paster make-config 命令来通过这个模板创建配置文件。发行和部署工程可以参考 发行工程 ,而最终用户可以参考 安装应用

注意 data 目录,这个目录可以被 development.ini 文件所配置,并用于保存缓存数据和会话。

helloworld 中的 helloworld 目录用于放置应用程序的特定文件。目录结构如下:

-helloworld
    -helloworld
        -config
        -controllers
        -docs
        -i18n
        -lib
        -models
        -public
        -templates
        -tests
        -__init__.py
        -websetup.py

config 目录包含了WEB应用的配置选项。

controllers 目录放置控制器。控制器是程序的核心,决定了装入哪些数据,和如何显示。

docs 目录用于放置程序的文档。你可以使用 setup.py pudge 来将他们转换为HTML。

i18n 目录用来放置多语言信息编目。

lib 目录用来放置各个控制器之间的共享模块,第三方代码,或者其他合适的代码。

models 目录用来放置模型对象,比如ORM使用的类。在 models/__init__.py 中的对象定义将会被装入并作为模型 model.YourObject 。数据库的配置字符串保存在 development.ini 文件。

public 目录用来放置HTML、图像、JavaScript、CSS等等静态文件。有如apache的htdocs目录。

tests 目录用来放置控制器等等的单元测试。控制器的单元测试使用Nose和 paste.fixture 。

templates 目录来保存模板。模板包含混合的静态文本和Python代码,用来生成HTML文档。Pylons使用Myghty模板作为缺省,但是同时支持Cheetah、Kid等等其他多种模板,这些需要通过Buffet来调用。查看 如何使用其他模板

__init__.py 文集那将 helloworld 目录作为一个Python包来发布。

websetup.py 文件包含了用户在运行 paster setup-app 时运行的代码,参考 安装应用 。如果希望在应用执行之前运行一些代码,这里比较好。

5.3   尝试使用模板

我们可以尝试一个模板工程如下:

$ cd helloworld
$ paster serve --reload development.ini

这些命令会使用配置development.ini进行启动并提供Pylons服务。

选项 –reload 会在Python代码或 development.ini 文件发生改变时自动重启服务器。这在开发中是非常有用的。

如果访问 http://127.0.0.1:5000/ 你可以看到欢迎页面( 127.0.0.1 是本机IP),但是可以通过 development.ini 文件来修改。

尝试创建一个新文件 test.html 在目录 helloworld/public 中,内容如下:

<html>
    <body>
        Hello world!
    </body>
</html>

如果访问 http://127.0.0.1:5000/test.html 可以看到 Hello World! 这个信息。任何在 public 目录下的文件都回按照相同的名字对外提供服务,Pylons有个选项可以决定从 public 目录返回,还是从代码返回。这个行为可以通过修改 config/middleware.py 文件中的 Cascade 的顺序来改变。

5.4   交互调试器

交互调试器是开发中的强大工具。他在使用 development.ini 作为配置时是默认启用的。当启用时,他允许通过一个WEB页面寻找错误发生的位置。在产品环境中调试器将会降低安全级别,所以通过 paster make-config 生成的产品环境配置文件需要关闭调试器。

关闭调试器,取消如下的注释,在 development.ini 文件的 [app:main] 节中:

# set debug=false

改为:

set debug=false

然后。将debug设置为false就是为了在产品环境提高安全系数。

更多相关信息参考 交互调试器 文档。

5.5   创建控制器并修改URL路由

你现在可以创建一个自己的WEB应用了。首先给hello world创建一个控制器:

$ paster controller hello

命令 paster 会创建 controllers/hello.py 文件,并同时创建其单元测试文件 helloworld/tests/functional/test_hello.py 用来运行这个控制器的单元测试。

如下是用于在 http://127.0.0.1:5000/hello 打印 ‘Hello world’ 的简单控制器代码。将这些代码放入 helloworld/controllers/hello.py 文件中:

from helloworld.lib.base import *
class HelloController(BaseController):
    def index(self):
        return Response('Hello world')

Pylons使用一个强大的并且灵活的URL路由来控制代码和URL的映射。

我们希望同时在 http://127.0.0.1:5000/hellohttp://127.0.0.1:5000/ 下显示,可以通过如下路由。将如下的行添加到配置文件 helloworld/config/routing.py ,如下:

map.connect('',controller='hello',action='index')
map.connect(':controller/:action/:id')
map.connect('*url',controller='template',action='view')

这样就可以将空URL匹配到 hello 控制器的 index 行为上,否则就按照 controller/action/id 的方式匹配,当然要确保这些可以匹配到。如果确实匹配不到了,就转到 templates 控制器的 view 行为,显示404错误。

改变了URL的路由之后必须重启服务器。但是如果你有开启 --reload 选项则会自动重启服务器,要不就要手动关闭和启动。

Note

Myghty模板的改变不需要重启服务器,也不需要 --reload 就可以起效。

访问 http://127.0.0.1:5000/hellohttp://127.0.0.1:5000/ 可以看到第一个显示 Hello world ,而第二个显示从前的欢迎页面。这是因为在 public 目录下的静态文件要优先于代码被显示。

删除文件 public/index.html 可以得到预期效果。更多信息参考 URL路由手册

5.6   模板和请求周期

当你的控制器的方法被WSGI应用所请求并返回Response对象时,如果希望使用模板返回,可以使用 command 命令,或者 render_response 命令。随之会处理模板并生成响应(Response)对象。

Note

如果希望获得更多关于 renderrender_reponse 的信息,参考 Pylons模板API

下面的例子模板,使用Myghty,打印一些信息。

创建一个模板文件 helloworld/templates/serverinfo.myt ,包含如下内容:

<p>Hi, here's the server environment: <br />
<% str(request.environ) %></p>
<p>
and here's the URL you called: <% h.url_for() %>
</p>

使用这个模板,在 helloworld 的HelloController控制器添加新方法如下:

def serverinfo(self):
    return render_response('/serverinfo.myt')

函数 render_response('/serverinfo.myt') 会使用默认引擎(Myghty)处理模板。

如果服务器仍然在运行,可以查看 http://127.0.0.1:5000/hello/serverinfo

也可以简单的重启服务器,在helloworld目录执行 paster serve --reload development.ini

5.7   使用Sessions

会话的处理来自应用程序的 Beaker middleware ,其包含Myghty的容器API。提供了健壮和强大的Session和缓存能力。

使用Session是很容易的,如下是保存Session的例子:

def serverinfo(self):
    session['name']='George'
    session.save()
    return render_response('/serverinfo.myt')

Session的选项可以通过 development.ini 来定制,并且使用相同的名字,详见 Myghty docs for sessions

Note

记住每次调用 session.save() 在返回相应之前来确保Session的保存。

5.8   控制器变量和模板全局变量

5.8.1   Pylons全局变量

为了方便,有少数几个通过导入 lib.base 就可以使用的全局变量,你可以在控制器中随意使用:

sessioin :存储Session数据,参考 Myghty Session docs

request :请求对象。

Response :响应类,控制器一般来说应该返回这个类的实例。

abort :用于中断请求,并发送 HTTPException ,可以附带状态码。

redirect_to :通过HTTP 302状态码来重定向浏览器到其他地址的函数,通过HTTPException实现。

render :用来使用模板生成字符串的函数。

render_response :用来使用模板生成 Response 对象的函数,相当于 Response(render(...))

h :用于引用Pylons的其他工具函数。缺省时,Pylons会将工具函数装入自 Web Helper 包。阅读文档时应该记住这些函数都可以通过 h 来引用。在Pylons下面通过这个作为命名空间。

c :用来向模板传递变量。

g :应用程序全局变量对象,可持续的。

5.8.2   传递变量到模板

Pylons控制器被每个请求创建一次。这意味着你可以在 self 上面绑定需要使用的变量。当然,这样做不太好跟踪,除非你需要传递到模板。如果希望传递传递数据到模板,可以使用变量 c 。因为Myghty模板默认就是使用 c 作为全局变量的。下面是使用的例子:

def serverinfo(self):
    c.value=2
    return render_response('/serverinfo.myt')

然后修改 templates 目录中的模板 serverinfo.myt 文件成如下:

<p>The value of <tt>c.value</tt> is:
<% c.value %>

Note

c对象在其他模板语言中也是适用的。

你可以看到打印出来的 2 。当访问c的属性不存在时,不会发生错误,而是返回空字符串。这对控制响应行为很有用。例如:

<p>Hi there <% c.name or c.full_name or "Joe Smith" %>

Warning

不要随意设置以 “_” 开头的c的属性。c和其他很多全局变量都是 StackedObjectProxies 类的成员。这可能会与已有的内置方法发生冲突,导致一些难于调试的问题。

c对象在每次请求时都回初始化,所以不需要担心控制器使用了先前请求的旧值。

5.8.3   应用程序全局对象和持续对象

某些场合需要在各个控制器之间共享一些信息而不会在每个请求中重启。例如在应用程序启动时开启一个TCP连接。你可以通过 g 变量。

g 变量是一个 Globals 类的实例在文件 lib/app_globals.py 中。在 __init__() 中设置的属性对 g 来说都是应用级别的全局属性。在任何一个请求中对g的某个属性的修改都会保持下来。在请求中设置全局变量时必须小心。

下例是如何使用 g 变量。首先修改 lib/app_globals.py Globals 类,把 __init__() 方法如下:

def __init__(self,global_conf,app_conf,**extra):
    self.message='Hello'

然后添加一个新方法到 helloworld/controllers/hello.py 如下:

def app_globals_test(self):
    resp=Response()
    if g.message=='Hello':
        resp.write(g.message)
        g.message='Hello World!'
    else:
        resp.write(g.message)
    return resp

这时如果运行服务器在 http://127.0.0.1:5000/hello/app_globals_test ,你可以看到信息 Hello 。如果再次访问,将会改变到 Hello World! ,然后就保持这样的改变了,因为在首次请求时,变量就已经改变了。

__init__() 方法需要参数 global_confapp_conf ,这些配置保存在文件 development.ini 中。这意味着可以在这里就设置全局变量。注意在第一个请求没有建立之前不可以设置变量c和h。

可以给全局变量添加 __del__() 方法,确保在程序退出时执行。

6   《Windows用户安装后必读》笔记

安装后必读

7   调试中发现的问题

7.1   URL映射的定义

必须在一开始就进行URL映射的定义,否则无法从URL上访问到自己的模块。在文件 config/routing.py 中定义。定义格式如下:

map.connect('urlpath',controller='controller_name',action='index')

其中的action是干什么是不知道的,重要的只是指定URL和控制器。而且要注意,这个定义必须写在 make_map() 函数中,并且必须在如下的两个语句之前定义,因为这两个语句会尽可能匹配多的URL,放在这后面不会起作用:

map.connect(':controller/:action/:id')
map.connect('*url',controller='template',action='view')

当然,这种使用URL映射的方式,可以很好自定义URL的样式。

另外,只要手动重启服务器,不去 routing.py 定义URL映射,也是可以直接使用模块名来访问的。当然,这个所谓模块名是不包含 .py 的。

7.2   参数传递,方法参数传递

Pylons用于暴露的方法是允许接受参数的,当然,如果硬是不传递,也没有设置默认值的时候会由Pylons好心的传递一个None过来。这里有个例子:

class HappyController(...):
    def test(self,id):
        return Response('test'+id)

当然,顶层类是使用 paster controller happy 自动生成的,所以在URL中也是使用 happy 这个词的。而 test 则是其暴露出来供访问的方法。这时有几个URL可供调用,结果分别为:

URL 解释
/happy/test 返回异常,报告id为None不可以连接字符串
/happy/test/ 返回异常,报告id为None不可以连接字符串
/happy/test/50 id得到参数50,返回 test50

当然这里注意,这个暴露方法参数只是通过URL的分级方式提供的,而问号之后的查询参数是不会传递到这里的。

还要注意routing.py中的一句:

map.connect(':controller/:action/:id')

这句的意思是允许以 控制器名/方法名/id 的方法来调用,作为一种默认的调用方法。而注意这个 :id ,就是说,方法的参数名只能是 id ,如果使用其他的则会发生错误。

7.3   共享模块代码

工程目录中的lib目录可以用于放置一些共享模块。调用的方法么,举个例子,比如有个模块的文件名是helloworld/lib/testunit.py,那么导入语句就可以写作:

import helloworld.lib.testunit

这样,可以在lib目录中放置数据库访问模块等等,多种东东。

Python的logging模块

Tuesday, September 11th, 2007

Python的logging模块

翻译: gashero <harry.python@gmail.com>

目录

1   简介-Python文档

从Python2.3版本中开始引入的logging模块为应用提供了灵活的日志系统。

logging的行为依靠调用 Logger 类的方法来实现,实例一般叫做logger。每个实例都拥有自己的名字,并且可以通过点来分割具备层次的名字。例如,一个logger叫做”scan”是一个叫做”scan.text”的logger的顶层,当然也包括”scan.html”和”scan.pdf”。logger的名字指示了他属于应用程序的哪个位置。

日志信息也拥有级别指定其重要程度。缺省提供的级别包括DEBUG,INFO,WARNING,ERROR,CRITICAL(严重)。为了方便,你可以调用特定的方法来指定日志的重要性,对应的方法为 debug()info()warning()error()critical() 。你也不必拘泥于这些级别,你可以使用更为通用的方法 log() 并且手动指定其级别。

日志级别对应值如下表所示。了解这些有助于你定义自己的日志级别的相对关系。如果你定义的级别与已有的数值相同,则原有的级别会被覆盖。

级别 数值
CRITICAL 50
ERROR 40
WARNING 30
INFO 20
DEBUG 10
NOTSET 0

日志级别也可以在指定logger时定义,可以在开发时装入和存储配置。当调用logger的日志方法时,logger对比日志级别和自己的被指定的级别。如果本身的级别高于调用方法的级别,则不会记录这个日志信息。这是一个方便的方法用于忽略过多细节的日志。

日志信息会被编码为 LogRecord 类,当logger决定记录一个事件时,就会为日志信息创建一个LogRecord实例。

日志信息会被发布(dispatch)机制通过handler来发布出去,这些handler继承自 Handler 类。Handler负责把日志信息(LogRecord的实例)发布到对应的位置供处理这些信息。Handler传递LogRecord实例并指定特定目标。每个logger都可以拥有0个或多个handler,通过 addHandler() 方法添加。除了handler与logger的组合之外,所有的handler还会被所组合的祖先logger所调用来发布信息。

有如logger,handler也可以有自己的级别过滤器,用于过滤日志级别。如果一个handler决定发布一个事件,可以调用 emit() 来发送信息到目的。大多数用户继承的Handler的子类需要重载这个emit()方法。

除了基本的Handler类,比较有用的子类如:

  1. StreamHandler实例发送错误到流(类似文件的对象)。
  2. FileHandler实例发送错误到磁盘文件。
  3. BaseRotatingHandler是所有轮徇日志的基类,不能直接使用。但是可以使用RotatingFileHandler和TimeRotatingFileHandler。
  4. RotatingFileHandler实例发送信息到磁盘文件,并且限制最大的日志文件大小,并适时轮徇。
  5. TimeRotatingFileHandler实例发送错误信息到磁盘,并在适当的事件间隔进行轮徇。
  6. SocketHandler实例发送日志到TCP/IP socket。
  7. DatagramHandler实例发送错误信息通过UDP协议。
  8. SMTPHandler实例发送错误信息到特定的email地址。
  9. SysLogHandler实例发送日志到UNIX syslog服务,并支持远程syslog服务。
  10. NTEventLogHandler实例发送日志到WindowsNT/2000/XP事件日志。
  11. MemoryHandler实例发送日志到内存中的缓冲区,并在达到特定条件时清空。
  12. HTTPHandler实例发送错误信息到HTTP服务器,通过GET或POST方法。

StreamHandler和FileHandler类都是在核心logging模块中定义的。其他handler定义在各个子模块中,叫做logging.handlers。当然还有一个logging.config模块提供了配置功能。

日志信息在输出之前会经过 Formatter 类的格式化。他们在最开始使用%操作符来设置。

批量格式化信息,需要 BufferingFormatter 类。除了格式化字符串之外,它还会在输出信息之前加上其他信息。

当基于logger和handler的级别过滤器仍然不够时,可以使用 Filter 类的实例来添加到logger和handler实例中,通过他们的 addFilter() 方法。当决定输出日志之前,logger和handler会询问过滤器,如果过滤器返回False,则不会显示这条日志。

最简单的过滤器功能用于指定日志的名字。如果使用了这个过滤器,则发送到命名logger和其子logger的日志将会通过,其他的会被丢弃。

除了上面所描述的,还有很多模块级别的函数。如下:

getLogger([name])

返回一个logger,可以指定名字,如果没有指定名字则返回根logger。指定的名字典型的为以点分隔的分层次的名字。选择一个恰当的名字,让别人知道,谁在输出日志。所有使用相同名字调用这个函数都会返回相同的logger实例。这意味着logger实例不需要在应用中到处传递。

getLoggerClass()

返回一个标准的Logger类,或者上次传递到 setLoggerClass() 的类。这个函数用于定义一个新的日志类,但是之前的代码不会受到影响。例如:

class MyLogger(logging.getLoggerClass()):
    #重载一些行为

debug(msg[,*args[,**kwargs]])

记录一个DEBUG级别的日志信息到根logger。 msg 参数就是用于输出的日志, args 参数是用于填入msg字符串的参数,当然kwargs也是差不多的角色。这说明msg可以是一个包含%d、%s等等的待格式化字符串。

在kwargs中有2个参数是必须的: exc_info 如果不为False,则异常产生的信息也会被记录;如果提供了一个异常tuple(以 sys.exc_info() 格式),则会使用它;否则会调用 sys.exc_info() 来获取异常信息。

另外一个参数为 extra 用于传递一个字典表示 LogRecord 中的 __dict__ 信息,可以加入用户自定义属性。这些传递的参数可以随你喜欢的。例如可能需要传递的:

FORMAT="%(asctime)-15s %(clientip)s %(user)-8s %(message)s"
logging.basicConfig(format=FORMAT)
dict={'clientip':'192.168.1.1','user':'fbloggs'}
logging.warning("Protocol problem: %s","connection reset",extra=d)

将会有如下的显示:

2006-02-08 22:20:02,165 192.168.1.1 fbloggs Protocol problem: connection reset

extra字典中的键不会与已有的键相冲突,具体可以参考Formatter的文档中关于使用字典的部分。

在选择字典中的键名时,必须小心一些情况。上面的例子中,已经在待格式化字符串中有’clientip’和’user’了,这时如果没有传递这两个键,则不会输出日志,因为出现了异常了。这种情况时,你必须要传递这2个键。

出于这种复杂性,这种功能往往用在很特殊的地方,比如多线程服务器,或关心多种信息的上下文环境,例如远程IP和登录用户。在这些情况中,需要先指定Formatter实例。 extra 参数从2.5版本开始加入。

info(msg[,*args[,**kwargs]])

记录INFO级别的日志信息,其他参数同debug()。

warning(msg[,*args[,**kwargs]])

记录WARNING级别的日志信息,其他参数同debug()。

error(msg[,*args[,**kwargs]])

记录ERROR级别的日志信息,其他参数同debug()。

critical(msg[,*args[,**kwargs]])

记录CRITICAL级别的日志信息,其他参数同debug()。

exception(msg[,*args])

记录ERROR级别的日志信息,其他参数同debug()。一般用在异常处理中。

log(level,msg[,*args[,**kwargs]])

记录level级别的日志信息,其他参数同debug()。

disable(lvl)

提供一个在logger中禁用某个级别日志的方法,用于应用临时切换可显示的日志。

addLevelName(lvl,levelName)

给级别lvl指定一个名字levelName到内部字典。以后用于映射数字的级别到特定的名字,供给Formatter用来格式化字符串。也可以用于定义自己的级别。这里也是唯一可以注册级别用的地方,级别必须是数字,并且在按照一定的排序要求。

getLevelName(lvl)

返回lvl级别的文字说明。如果级别是CRITICAL、ERROR、WARNING、INFO、DEBUG中的某一个,也会返回对应的名字。如果你自己通过addLevelName()自定义过级别,那么也会返回对应的名字。如果对应级别不存在,则返回 "Level %s"%lvl 这个字符串。

makeLogRecord(attrdict)

创建并返回一个LogRecord实例,并使用attrdict赋值过。这个函数用于把一个LogRecord给pickle过后通过socket发送,并方便接收方重组这个信息。

basicConfig([**kwargs])

对日志系统进行基本配置,使用缺省的StreamHandler和Formatter并添加根logger。一些函数如debug()、info()、warning()、error()、critical()会自动使用basicConfig()配置好的根logger进行日志输出。在2.4版之前,basicConfig()不接受字典参数。

如下是支持的字典参数:

格式 描述
filename 指定FileHandler的文件名,而不是StreamHandler
filemode 打开文件的模式,同open函数中的同名参数,默认为’a’
format 输出格式字符串
datefmt 日期格式
level 设置根logger的日志级别
stream 指定StreamHandler。这个参数与filename冲突,忽略stream

shutdown()

告知日志系统准备关闭日志并将所有信息写入磁盘。

setLoggerClass(klass)

告知日志系统使用类klass作为示例的Logger类。这个类应该定义 __init__() 方法并接受一个参数表示名字,而且 __init__() 方法应该在内部调用 Logger.__init__() 方法。这个函数一般在应用自定义Logger的行为时使用。

PEP 282, A Logging System

Original Python logging package

2   各种Handler的子类

2.1   TimedRotatingFileHandler

TimedRotatingFileHandler 类是在logging.handler包中,支持对写入到磁盘的日志按照时间间隔来轮询。

class TimedRotatingFileHandler(filename[,when[,interval[,backupCount]]])

返回一个新的TimedRotatingFileHandler类实例,在指定文件名时会自动使用词缀。轮询发生的时间取决于参数 when 和 interval 。

你可以使用 when 来指定 interval 的类型。可选值如下,注意区分大小写:

interval的类型
S
M 分钟
H 小时
D
W
midnight 在午夜

如果 backupCount 为非零,则系统会在旧日志文件名的末尾添加词缀。词缀格式为日期和时间,使用 strftime 格式如 %Y-%m-%d %H:%M:%S 或者依赖于之前的规则。最多会保留 backupCount 个日志文件,如果继续增加,会持续删除最旧的的日志。

doRollover()

进行一次回转,有如上面描述的。

emit(record)

把日志输出到文件,按照如上的回转规则。

DBUtils超快速入门指南

Tuesday, September 11th, 2007

DBUtils超快速入门指南

版本: 0.9.2

目录

1   简介

DBUtils是一套Python数据库连接池包,并允许对非线程安全的数据库接口进行线程安全包装。DBUtils来自Webware for Python。

DBUtils提供两种外部接口:

  • PersistentDB :提供线程专用的数据库连接,并自动管理连接。
  • PooledDB :提供线程间可共享的数据库连接,并自动管理连接。

实测证明 PersistentDB 的速度是最高的,但是在某些特殊情况下,数据库的连接过程可能异常缓慢,而此时的PooledDB则可以提供相对来说平均连接时间比较短的管理方式。

另外,实际使用的数据库驱动也有所依赖,比如SQLite数据库只能使用PersistentDB作连接池。

下载地址:

http://www.webwareforpython.org/downloads/DBUtils/

2   使用方法

连接池对象只初始化一次,一般可以作为模块级代码来确保。

PersistentDB的连接例子:

import DBUtils.PersistentDB
persist=DBUtils.PersistentDB.PersistentDB(dbpai=MySQLdb,maxusage=1000,**kwargs)

这里的参数dbpai指使用的底层数据库模块,兼容DB-API的。maxusage则为一个连接最大使用次数,参考了官方例子。后面的**kwargs则为实际传递给MySQLdb的参数。

获取连接:

conn=persist.connection()

实际编程中用过的连接直接关闭 conn.close() 即可将连接交还给连接池。

PooledDB使用方法同PersistentDB,只是参数有所不同。

  • dbapi :数据库接口
  • mincached :启动时开启的空连接数量
  • maxcached :连接池最大可用连接数量
  • maxshared :连接池最大可共享连接数量
  • maxconnections :最大允许连接数量
  • blocking :达到最大数量时是否阻塞
  • maxusage :单个连接最大复用次数
  • setsession :用于传递到数据库的准备会话,如 ["set name UTF-8"]

一个使用过程:

db=pooled.connection()
cur=db.cursor()
cur.execute(...)
res=cur.fetchone()
cur.close() # or del cur
db.close() # or del db

DBUtils 用户指南(更新至0.9.3)

Tuesday, September 11th, 2007

DBUtils 用户指南

版本: 0.9.3

目录

摘要

DBUtils 是一套允许线程化 Python 程序可以安全和有效的访问数据库的模块。DBUtils已经作为 Webware for Python 一部分用来结合 PyGreSQL 访问 PostgreSQL 数据库,当然他也可以用在其他Python应用程序中来访问 DB-API 2 兼容的数据库接口。

模块

DBUtils实际上是一个包含两个子模块的Python包,一个用于连接DB-API 2模块,另一个用于连接典型的PyGreSQL模块。

全局的DB-API 2变量
SteadyDB.py 用于稳定数据库连接
PooledDB.py 连接池
PersistentDB.py 维持持续的数据库连接
SimplePooledDB.py 简单连接池
典型的 PyGreSQL 变量
SteadyPg.py 稳定PyGreSQL连接
PooledPg.py PyGreSQL连接池
PersistentPg.py 维持持续的PyGreSQL连接
SimplePooledPg.py 简单的PyGreSQL连接池

对标准DB-API 2模块的依赖如下图所示:

dbdep.gif对典型的PyGreSQL模块依赖如下图所示:

pgdep.gif

下载

你可以从 Webware 的网站下载最新版本:

http://www.webwareforpython.org/downloads/DBUtils/

也可以从Python Package Index来下载:

http://www.python.org/pypi/DBUtils/

安装

安装为顶层模块

如果你打算在除了Webware之外的程序中使用,推荐安装为顶层模块:

python setup.py install

安装为Webware的子模块(插件)

如果你只是打算在Webware中使用,则可以按照如下安装:

python setup.py install --install-lib=/path/to/Webware

替换 /path/to/Webware 为Webware安装的根路径。你还需要运行Webware的安装程序来同时包含DBUtils的文档:

cd path/to/Webware
python install.py

功能

这一节的主要例子面向DB-API 2,但是也适用于典型的PyGreSQL模块。

SimplePooledDB

DBUtils.SimplePooledDB 是一个非常简单的数据库连接池实现。他比完善的 PooledDB 模块缺少很多功能。 DBUtils.SimplePooledDB 本质上类似于 MiscUtils.DBPool 这个Webware的组成部分。你可以把它看作一种演示程序。

SteadyDB

DBUtils.SteadyDB 是一个模块实现了”强硬”的数据库连接,基于DB-API 2建立的原始连接。一个”强硬”的连接意味着在连接关闭之后,或者使用次数操作限制时会重新连接。

一个典型的例子是数据库重启时,而你的程序仍然在运行并需要访问数据库,或者当你的程序连接了一个防火墙后面的远程数据库,而防火墙重启时丢失了状态时。

一般来说你不需要直接使用 SteadyDB 它只是给接下来的两个模块提供基本服务, PersistentDBPooledDB

PersistentDB

DBUtils.PersistentDB 实现了强硬的、线程安全的、顽固的数据库连接,使用DB-API 2模块。如下图展示了使用 PersistentDB 时的连接层步骤:

persist.gif当一个线程首次打开一个数据库连接时,一个连接会打开并仅供这个线程使用。当线程关闭连接时,连接仍然持续打开供这个线程下次请求时使用这个已经打开的连接。连接在线程死亡时自动关闭。

简单的来说 PersistentDB 尝试重用数据库连接来提高线程化程序的数据库访问性能,并且他确保连接不会被线程之间共享。

因此, PersistentDB 可以在底层DB-API模块并非线程安全的时候同样工作的很好,并且他会在其他线程改变数据库会话或者使用多语句事务时同样避免问题的发生。

PooledDB

DBUtils.PooledDB 实现了一个强硬的、线程安全的、有缓存的、可复用的数据库连接,使用任何DB-API 2模块。如下图展示了使用 PooledDB 时的工作流程:

pool.gif如图所示 PooledDB 可以在不同线程之间共享打开的数据库连接。这在你连接并指定 maxshared 参数,并且底层的DB-API 2接口是线程安全才可以,但是你仍然可以使用专用数据库连接而不在线程之间共享连接。除了共享连接以外,还可以设立一个至少 mincached 的连接池,并且最多允许使用 maxcached 个连接,这可以同时用于专用和共享连接池。当一个线程关闭了一个非共享连接,则会返还到空闲连接池中等待下次使用。

如果底层DB-API模块是非线程安全的,线程锁会确保使用 PooledDB 是线程安全的。所以你并不需要为此担心,但是你在使用专用连接来改变数据库会话或执行多命令事务时必须小心。

该选择哪一个?

PersistentDBPooledDB 都是为了重用数据库连接来提高性能,并保持数据库的稳定性。

所以选择何种模块,可以参考上面的解释。 PersistentDB 将会保持一定数量的连接供频繁使用。在这种情况下你总是保持固定数量的连接。如果你的程序频繁的启动和关闭线程,最好使用 PooledDB 。后面将会提到更好的调整,尤其在使用线程安全的DB-API 2模块时。

当然,这两个模块的接口是很相似的,你可以方便的在他们之间转换,并查看哪个更好一些。

使用方法

所有模块的使用方法都很相似,但是在初始化 “Pooled” 和 “Persistent” 时还有有些不同,尤其是DB-API和PyGreSQL之间。

这里只讲解 PersistentDB 和更复杂的 PooledDB 模块。其他模块的细节请参与其文档。使用Python解释器控制台,你可以显示 PooledDB 的文档,如下:

help(PooledDB)

PersistentDB

为了使用 PersistentDB 你首先需要通过创建 PersistentDB 的实例来设置一个特定数据库连接的生成器,床底如下参数:

  • dbapi: 需要使用的DB-API 2兼容的数据库模块
  • maxusage: 一个连接最大允许复用次数(缺省为 0False 意味着无限制的重用),当达到限制时,将会重新连接数据库
  • setsession: 一个可选的SQL命令列表可以用于准备会话,如 ["set datestyle to german", ...]
  • 其他的,你还可以传递用于传递给真实的DB-API 2模块的参数,例如主机名、数据库、用户名、密码等。

举个例子,如果你正在使用 pgdb 作为数据库模块并想要连接本机数据库 mydb ,允许重用1000次:

import pgdb # import used DB-API 2 module
from PersistentDB import PersistentDB
persist = PersistentDB(pgdb, 1000, database='mydb')

按照如上设置完成了连接生成器之后,你可以按照如下来请求一个连接:

db = persist.connection()

你可以使用这些连接就像使用原始的DB-API 2连接一样。实际上你得到的是一个通过SteadyDB得到的强硬的连接,基于DB-API 2。

关闭一个强硬的连接使用 db.close() ,这在内部实际上被忽略掉了,并且供下次使用。在线程关闭时,也会自动关闭数据库连接。你可以改变这个行为通过 persist._closeableTrue

PooledDB

为了使用 PooledDB 模块,你首先需要通过创建 PooledDB 来设置数据库连接池,传递如下参数:

  • dbapi: 需要使用的DB-API 2模块
  • mincached : 启动时开启的空连接数量(缺省值 0 意味着开始时不创建连接)
  • maxcached: 连接池使用的最多连接数量(缺省值 0 代表不限制连接池大小)
  • maxshared: 最大允许的共享连接数量(缺省值 0 代表所有连接都是专用的)如果达到了最大数量,被请求为共享的连接将会被共享使用。
  • maxconnections: 最大允许连接数量(缺省值 0 代表不限制)
  • blocking: 设置在达到最大数量时的行为(缺省值 0False 代表返回一个错误;其他代表阻塞直到连接数减少)
  • maxusage: 单个连接的最大允许复用次数(缺省值 0False 代表不限制的复用)。当达到最大数值时,连接会自动重新连接(关闭和重新打开)
  • setsession: 一个可选的SQL命令列表用于准备每个会话,如 ["set datestyle to german", ...]
  • 其他,你可以设置用于传递到真正的DB-API 2的参数,例如主机名、数据库、用户名、密码等。

举个例子,如果你正在使用 pgdb 作为DB-API模块,并希望连接池中至少有5个连接到数据库 mydb

import pgdb # import used DB-API 2 module
from PooledDB import PooledDB
pool = PooledPg(pgdb, 5, database='mydb')

一旦设置好了连接池,你就可以按照如下请求一个连接:

db = pool.connection()

你可以使用这些连接有如原始的DB-API 2一样。而实际使用的是SteadyDB版本的强硬连接。

请注意连接可以与其他线程共享,只要你设置 maxshared 参数为非零,并且DB-API 2模块也允许。如果你想要使用专用连接则使用:

db = pool.connection(0)

如果你不再需要这个连接了,则可以返回给连接池使用 db.close() 。你也可以使用相同的方法获取另一个连接。

警告: 在一个多线程环境,不要使用下面的方法:

pool.connection().cursor().execute(...)

这将会导致过早的释放连接以供复用,而且如果是非线程安全还会出错。确保连接对象在你的使用过程中是一直存在的,例如:

db = pool.connection()
cur = db.cursor()
cur.execute(...)
res = cur.fetchone()
cur.close() # or del cur
db.close() # or del db

在Webware中使用

如果你正在 Webware for Python 的 servlets 中使用DBUtils来存取数据库,你要确保数据库连接生成器只被应用启动一次,而不是每个servlet启动时都创建一个。为了达到这个目的,你可以在模块或类的初始化代码中添加这些代码,或者使用 __init__.py 中的 contextInitialize() 函数。

目录 Examples 是DBUtils发行包的一部分,包含了一个使用示例数据库的Webware的例子,用来跟踪演讲会的出席者(这个例子的主意来自Andrew Kuchling的 “The Python DB-API“)。

例子的正文可以通过创建配置文件 Configs/Database.config 来配置,改变例子 Examples/DBUtilsExample.py 的缺省参数。这种方式可以设置一个专用数据库的用户名和密码,你也可以选择底层的数据库模块。如果设置了 maxcached ,则例子会使用 “Pooled” 模块,否则会使用 “Persistent” 模块。

注意

如果你正在使用流行的ORM SQLObject ,你并不需要使用DBUtiils,因为他已经内含连接池了。 SQLObject 2 (SQL-API) 事实上还从DBUtils这里借用了连接池分层的代码。

未来功能

一些未来会使用的方法:

  • 一个连接最大被使用的次数,或一个连接最大活动时间。
  • 创建模块 MonitorDBMonitorPg 运行在单独的线程中,监控连接池中各个共享连接的状态。如果检测到一个损坏的连接,则会自动恢复这个连接。这在很多网站中是很实用的,因为晚上往往要重启数据库服务器。如果不使用监控线程,则用户要等到第二天早上才可以使用。正是因为如此,检测损坏的连接并自动恢复是很有用的。使用了监控线程之后,间断时间在晚上,而且很短。监控线程同样可以配置连接生成器的线程池,并且确保用户到达之前完成。
  • 可选的日志,记录损坏的连接和最大限制。

错误报告与回馈

请将错误报告、补丁、回馈直接发送给作者(使用下面给出的邮件地址)。

如果有Webware相关的问题,可以到邮件列表讨论 Webware for Python mailing list

链接

一些相关软件的链接:

作者列表

作者: Christoph Zwerschke <cito@online.de>

版权与许可

Copyright @ 2005-2006 by Christoph Zwerschke. All Rights Reserved.

DBUtils是一个自由开源软件,使用 Open Software License version 2.1 许可。

pysqlite使用指南

Tuesday, September 11th, 2007

pysqlite使用指南

最后更新: pysqlite 2.3.0

目录

1 简介

本使用指南并非Python、SQL、SQLite的入门指南;而是pysqlite功能的介绍,并且包含了使用模式的示例代码。这个指南将会指导你使用Python的数据库接口和SQLite的文档。

2 Python数据库接口2.0兼容

2.1 不兼容的选项

pysqlite包含不兼容的DB API 2.0的功能,如下:

  • 在cursor.description中并不包含类型信息

    cursor.description是一个元组包含如下字段(name,type_code,display_size,internal_size,precision,scale,null_ok)来定义每个列。特定的数据库接口需要至少name和type_code,但是有时cursor.description是内置的,pysqlite不只能检测到任何类型。所以,pysqlite只在cursor.description中填入name字段。其他的字段全部设置为None。

  • 没有类型对象

    同样的,模块级并没有STRING、BINARY、NUMBER、DATETIME、ROWID,并且也没什么用。

2.2 不支持的可选功能

Cursor
不支持nextset方法。这个方法没有实现是因为数据库引擎也没有实现单个游标返回多个结果集。

2.3 有名无实支持的可选功能

Cursor

  • arraysize 属性

    作为最基本的需求,这个属性的值用于支持 fetchmany 方法。不过改变这个值也没什么关系,因为数据库引擎根本就只支持一次获取一行。

  • setinputsizes 方法

    尽管提供了这个方法,但是什么都不做。

  • setoutputsize 方法

    什么活都不干的方法。

2.4 扩展与警告

pysqlite提供了大量的功能来实现对Python DB API的基本支持。大部分扩展在已经在本节的Native Database Engine Features and Extensions Beyond the Python DB API中有所介绍。

  • connect 函数

    参数 database 指定SQLite数据库的文件。一般指定文件系统的绝对或相对路径。

    这个链接函数支持如下可选参数:

    • timeout 当多个连接同时存取数据库时,并且其中一个进程修改了数据库,SQLite数据库会锁定(locked)直到事务被提交。这个超时参数指定连接会等待这个锁定多长时间,并抛出异常。缺省的超时参数是5.0秒。例如:
      sqlite.connect(database="mydb",timeout=10.0)
    • isolation_level pysqlite将会缺省的以”BEGIN”语句开始一个事务,当使用DML语句如INSERT/UPDATE/DELETE/REPLACE。一些用户并不想pysqlite暗自打开事务,而希望使用自动提交模式。其他用户想要pysqlite打开不同类型的事务,例如”BEGIN IMMEDIATE”。查看 控制事务 了解更多细节。注意你通过设置isolation_level属性可以选择不同的isolation级别。例如:
      # 打开自动提交模式
      con=sqlite.connect("mydb",isolation_level=None)
      # 将isolation_level设置到"IMMEDIATE"
      con.isolation_level="IMMEDIATE"
    • detect_types SQLite本来支持的类型包括TEXT、INTEGER、FLOAT、BLOB、NULL。如果你想使用其他类型,你需要自己添加支持。 detect_types 参数和使用自定义的 converters 来使用 register_converter 函数允许你做到这些。 detect_types 缺省是0,就是关闭,你可以设置 PARSE_DECLTYPES 和 PARSE_COLNAMES 的组合来改变设置的类型。参见 SQLite与Python类型 来了解更多。
      • sqlite.PARSE_DECLTYPES 这会

Warning

pause

3 简要入门

这个教会你如何初步的使用pysqlite。而并非是一个Python Database API入门,也不是覆盖其他使用的方面。

3.1 连接到数据库

例子1

连接到数据库文件 mydb

from pysqlite2 import dbapi2 as sqlite
con=sqlite.connect("mydb")

例子2

创建一个内存数据库:

from pysqlite2 import dbapi2 as sqlite
con=sqlite.connect(":memory:")

3.2 执行SQL语句

为了执行这一节,我们需要一个数据库按照如下定义:

CREATE TABLE people
(
    name_last   varchar(20),
    age         integer
);
INSERT INTO people (name_last,age) VALUES ('Yeltsin',72);
INSERT INTO people (name_last,age) VALUES ('Putin',51);

例子1

这个例子显示了一个打印 people 表格内容的最简单的例子:

from pysqlite2 import dbapi2 as sqlite
# 创建数据库连接到文件"mydb"
con=sqlite.connect("mydb")
# 获取游标对象
cur=con.cursor()
# 执行SELECT语句
cur.execute("SELECT * FROM people ORDER BY age")
# 获取所有行并显示
print cur.fetchall()

输出:

[(u'Putin', 51), (u'Yeltsin', 72)]

例子2

如下是另一个小例子展示了如何单行显示记录:

from pysqlite2 import dbapi2 as sqlite
con=sqlite.connect("mydb")
cur=con.cursor()
SELECT="SELECT name_last,age FROM people ORDER BY age, name_last"
# 1. 第一种显示记录的方法
cur.execute(SELECT)
for (name_last,age) in cur:
    print '%s is %d years old.'%(name_last,age)
# 2. 第二种显示记录的方法
cur.execute(SELECT)
for row in cur:
    print '%s is %d years old.'%(row[0], row[1])

输出:

Putin is 51 years old.
Yeltsin is 72 years old.
Putin is 51 years old.
Yeltsin is 72 years old.

例子3

如下的程序以表格的方式打印表格内容:

from pysqlite2 import dbapi2 as sqlite
FIELD_MAX_WIDTH=20
TABLE_NAME='people'
SELECT="SELECT * FROM %s ORDER BY age,name_last"%TABLE_NAME
con=sqlite.connect("mydb")
cur=con.cursor()
cur.execute(SELECT)
#打印表头
for fieldDesc in cur.description:
    print fieldDesc[0].ljust(FIELD_MAX_WIDTH),
print #结束表头行
print '-'*78

#每行打印一次值
fieldIndices=range(len(cur.description))
for row in cur:
    for fieldIndex in fieldIndices:
        fieldValue=str(row[fieldIndex])
        print fieldValue.ljust(FIELD_MAX_WIDTH),
    print

输出:

name_last               age
---------------------------------------------
Putin                   51
Yeltsin                 72

例子4

插入人员信息到 people 表格:

from pysqlite2 import dbapi2 as sqlite
con=sqlite.connect("mydb")
cur=con.cursor()
newPeople=(
    ('Lebed',53),
    ('Zhirinovsky',57),
)
for person in newPeople:
    cur.execute("INSERT INTO people (name_last,age) VALUES (?,?)",person)
#修改之后必须明确的提交
con.commit()

注意参数化的SQL语句。当处理重复语句时,这会更快并产生更少的错误,相对于手动生成SQL语句。

而上面的核心语句:

for person in newPeople:
    cur.execute("INSERT INTO people (name_last,age) VALUES (?,?)",person)

可以被重写为:

cur.executemany("INSERT INTO people (name_last,age) VALUES (?,?)",newPeople)

在例子4的后面的打印结果为:

name_last                 age
-------------------------------------------
Putin                     51
Lebed                     53
Zhirinovsky               57
Yeltsin                   72

4 数据库引擎的本地功能和Python DB API的扩展功能

4.1 创建用户自定义函数

SQLite支持用户自定义函数。使用pysqlite,你使用连接的 create_function 方法来创建新函数:

def create_function(self,name,numparams,func)

如下是参数说明:

  • name

    SQL函数的名字

  • numparams

    函数接收的参数数量,-1是接收不限制数量的参数

  • func

    Python函数

函数可以返回任何pysqlite支持的SQLite类型:unicode、str、int、long、float、buffer和None。任何用户自定义函数中的异常都会导致SQL语句的执行中断。

例子:

from pysqlite2 import dbapi2 as sqlite
import md5
def md5sum(t):
    return md5.md5(t).hexdigest()
con=sqlite.connect(":memory:")
con.create_function("md5",1,md5sum)
cur=con.cursor()
cur.execute("SELECT md5(?)", ("foo",))
print cur.fetchone()[0]

4.2 创建用户自定义聚合体类型

SQLite支持用户自定义聚合类型函数。使用

Warning

pause

4.3 创建和使用对比函数

Warning

pause

4.4 检查语句完整性

Warning

pause

4.5 启用SQLite的共享缓存

Warning

pause

4.6 设置认证回调函数

Warning

pause

5 SQLite与Python类型

5.1 介绍

详见 http://sqlite.org/datatype3.html

Warning

pause

5.2 使用Python类型的SQLite数据库存储适配器

Warning

pause

5.3 转换SQLite值到Python类型

Warning

pause

5.4 缺省的pysqlite适配器和转换器

Warning

pause

6 控制事务

缺省时,pysqlite在DML语句(INSERT/UPDATE/DELETE/REPLACE)语句之前自动开启一个事务,并且在一个非DML语句或非DQL语句之前自动提交(除了SELECT/INSERT/UPDATE/DELETE/REPLACE)语句之外。

所以如果你在一个事务当中,要执行类似 CREATE TABLE … , VACUUM , PRAGMA 之类的命令,则pysqlite会在执行这些命令之前提交。有两个原因需要这么做。首先,大多数此类命令并不是工作在事务当中的。第二个原因是pysqlite需要保持对事务状态的跟踪,无论事务处于活跃状态与否。

你可以自己控制何种类型的”BEGIN”语句会自动执行(没有,或者所有),通过设置 connect 的 isolation_level 参数,或者连接对象的 isolation_level 属性。

如果你想要开启 autocommit 模式,设置 isolation_level 为None。

否则让这个值为缺省,将会使用平坦的”BEGIN”语句。或者设置一个SQLite支持的隔离(isolation)级别:DEFERRED(延时)、IMMEDIATE(立即)或EXCLUSIVE(独占)。

因为pysqlite需要对事务状态进行跟踪,所以你将不能使用 OR ROLLBACK 或 ON CONFLICT ROLLBACK 。作为交换,捕捉 IntegrityError 异常并手动调用连接的 rollback 方法。

7 高效的使用pysqlite

7.1 使用命令的捷径

使用非标准的连接的 execute() 、 executemany() 、 executescript() 方法,你的代码可以更加简明,因为你不需要再创建多余的游标对象了。作为交换,游标对象会隐含的创建并使用。这种方法,你可以直接在对象之上使用SELECT语句并使用序列方法。:

from pysqlite2 import dbapi2 as sqlite
persons=[
    ("Hugo","Boss"),
    ("Calvin","Klein")
    ]
con=sqlite.connect(":memory:")
#创建表格
con.execute("CREATE TABLE person (firstname,lastname)")
#填写表格
con.executemany("INSERT INTO person(firstname,lastname) VALUES (?,?)",
    persons)
#打印表格内容
for row in con.execute("SELECT firstname,lastname FROM person"):
    print row
#使用一个哑WHERE子句来让SQLite删除表格
print "I just deleted",con.execute("DELETE FROM person WHERE 1=1").rowcount,"rows"

7.2 使用列名而不是序号存取字段

一个pysqlite2.1.0的新功能是新的内建的sqlite.Row类设计作为记录工厂。 Rows包装了这个类并允许同时使用列序号(类似元组)和大小写不敏感的列名:

from pysqlite2 import dbapi2 as sqlite
con=sqlite.connect("mydb")
con.row_factory=sqlite.Row
cur=con.cursor()
cur.execute("SELECT name_last,age FROM people")
for row in cur:
    assert row[0] == row["name_last"]
    assert row["name_last"] == row["nAmE_lAsT"]
    assert row[1] == row["age"]
    assert row[1] == row["Age"]

Firebird笔记

Monday, September 10th, 2007

Firebird笔记

目录

1   选用理由

我正在开发一个爬虫,原来使用的SQLite数据库的多线程并发操作很麻烦,我一直都没能找到比较好的方法来避免数据库锁定,所以在尝试了一个多月之后我最终放弃了。放弃之后使用了BerkeleyDB作为数据库,自己写接口。运行过程尽管还算很快,但是也有问题,就是多线程操作BerkeleyDB时会让Python解释器出现段错误而退出,这也是无法忍受的错误。

所以在权衡之后我最终还是决定捡起一度放弃的Firebird数据库。决定未来将其集成到我的爬虫当中。另外,我的另外一个产品也需要使用足够健壮的数据库服务器。

关于MySQL,我还是很信赖其稳定性的,使用感受也不错,但是在我的程序中并不需要复杂的权限控制,而且通过网络连接数据库的安全性和速度也让我很讨厌。所以选用了Firebird的嵌入式版本。

2   与Python的结合使用

Firebird的Python接口叫做KinterbasDB,支持所有版本的Firebird和部分版本的Interbase。尽管支持,但是对1.0、1.5、2.0版本的Firebird是使用不同的安装包来支持的。

在Windows 2000系统之下我使用了 kinterbasdb-3.2.win32-FB-2.0-py2.4.exe 来进行安装,版本为3.2,对应Firebird版本为2.0。双击然后一路继续就完成了。

3   安装Firebird的嵌入式版本

因为考虑到未来程序的可移植性,所以我坚决避免使用Firebird的安装包进行安装。在安装完成 KinterbasDB 之后尝试在Python中导入:

>>> import kinterbasdb

发生了错误,提示找不到DLL文件,然后我使用了压缩包 Firebird-2.0.0.12748-0_embed_win32.zip 。对缺少的DLL文件依次从压缩包中解压出来并放置到目录 C:Python24Libsite-packageskinterbasdb 目录中。因为我发现这个目录是可以被路径识别的,并且跟 KinterbasDB 的关系密切。如下几个文件是导入语句所必须的:

  • fbclient.dll :最初叫做 fbembeb.dll 需要复制过来以后该名为新名字的,否则 KinterbasDB 是不认的。
  • icuuc30.dll :不知道干什么的,反正必须要有。
  • icudt30.dll :不知道干什么的。
  • icuin30.dll :不知道干什么的。

这四个动态链接库的大小就已经达到了 3.68MB ,不过面前还是可以接受的。

在这样一番折腾之后就可以成功的导入了,做了一下dir,结果超长,此处也就略掉吧。FB支持的功能非常多,包括触发器、事务、存储过程之类的,这些东西在开源数据库当中是非常少有的。

4   《Firebird嵌入版本开发方案》笔记

Firebird的官方网站( http://www.ibphoenix.com )。这里还推荐了一个叫UIB的东西,网站是( http://www.progdigy.com/UIB/ )。原来用于Interbase的可视化管理工具IBExpert ( http://www.ibexpert.com )从某种程度上来说也可以用于Firebird。

拒作者说IBExpert好像是有限制的,所以推荐去 ftp://hdkej.8800.org 下载一个修改过的版本,没有限制。运行IBExpert新建数据库时服务器选择Local,Client Library选择fbclient.dll。

看来UIB是给Delphi使用的一套接口,Python的就免了。作者的调试过程发现不能让两个进程同时访问数据库。

5   博客 http://tb.blog.csdn.net 的Firebird使用 笔记

推荐下载IBExpert而已,另外就是FB内置了isql.exe在控制台进行数据库的基本操作。

6   《python2.4 连接firebird1.5 一个连接数据库的文件》笔记

作者使用Python2.4和KinterbasDB连接了FB数据库。首先是导入:

import kinterbasdb as kdb

初始化连接并返回连接对象:

conn=kdb.connect(dsn='lq:D:\\data\\aaa.FDB',user='sysdba',password='masterkey')
conn.text_factory=str

连接选项的dsn用来指定数据源,用户名密码sysdba:masterkey是默认的。第二行是用来避免 “Could not decode to UTF-8 column” 错误的。

因为很多时候程序需要随身携带数据库,所以需要使用相对路径,文中给出了获取当前路径的方法:

import os
ypath=os.path.abspath(os.path.dirname(sys.argv[0]))

如果需要通过列名来返回值,则可以作如下设置:

conn.row_factory=kdb.Row

通过ConfigParser模块支持INI格式的配置文件,如下是某配置文件:

[SQLFbDB]
dbname=data\aaa.FDB

如下是处理配置文件的代码:

import ConfigParser
config=ConfirParser.ConfigParser()
config.read('config.txt')
dbname=config.get('SQLFbDB','dbname')

7   《本地数据源:使用firebird数据库》笔记

Windows下的Firebird有classical和superserver两种版本。配置文件aliases.conf配置数据库别名,firebird.conf配置数据库参数如rootpath。

嵌入的FB数据库不再支持多用户访问和用户安全控制,不过对于本机程序来说很正常。

在.net下使用时有个连接选项叫server type,需要设置为1才是嵌入FB数据库。

8   Python-Chinese邮件列表上朋友的回复

  1. 安装KInterbasDB,会产生一个 lib\site-package\kinterbasdb 目录。

  2. 在kinterbasdb目录下新建一个embedded目录,然后将嵌入式的Firebird中的 fbembed.dllfirebird.msgib_util.dll 这3个文件放入embedded目录下。如果需要国际化支持,则同时复制intl子目录,这个目录包含 fbintl.dll 文件。

  3. fbembed.dll 文件改名为 fbclient.dll

  4. 这时就可以使用FB数据库了。

  5. KInterbasDB为了向后兼容,默认使用 mx.DateTime 模块,但是在Python2.4下并不需要而且多余。在Python标准库中的datetime模块更好用。需要用datetime替代mx.DateTime则按照如下修改:

    import kinterbasdb
    kinterbasdb.init(type_conv=200)

实际测试发现如果把几个动态链接库放在 embedded 目录中,则会提示动态链接库找不到。

使用twisted.python.log日志

Monday, September 10th, 2007

使用twisted.python.log日志

目录

版本: 2.4.0

1   简单使用

Twisted提供了一个简单而且可移植的日志系统叫做 twisted.python.log 。它包含3个函数:

msg

记录一条信息,例如:

from twisted.python import log
log.msg("hello, world")

err

把错误写入日志,包括traceback信息。你可以传递一个 failure 或者异常的实例,甚至什么都没有。如果传递其他的东西则会通过 repr() 函数获得字符串来显示。如果什么都不传递则会自动构造一个 Failure 实例,一般用于 except 子句:

try:
    x=1/0
except:
    log.err() #会自动记录ZeroDivisionError

startLogging

通过一个类似文件对象来开始日志,例如:

log.startLogging(open("/var/log/foo.log",'w'))

或者:

log.startLogging(sys.stdout)

缺省条件下,startLogging会同时将输出重定向到sys.stdout和sys.stderr。你可以在startLogging中设置setStdout=False来禁用这个功能。

1.1   日志与twistd

如果你使用twistd来运行你的程序作为后台进程,则他会自动托管startLogging,甚至还会自动轮训日志。查看 twistd and tac 一节或twistd的man手册了解更多。

1.2   日志文件

twisted.python.logfile 模块提供了一些可以同startLogging共同使用的类,例如 DailyLogFile ,提供了以天为单位的日志轮询。

2   编写日志监视器

日志监视器是Twisted日志系统的基础。一个日志监视器的例子是供startLogging使用的 FileLogObserver ,可以把事件写入文件中。一个日志监视器是可调用的,并且只接受一次字典作为参数。随后你可以用它来接收所有的日志事件(当然也会给其他日志监视器):

twisted.python.log.addObserver(yourCallable)

字典至少有2个项目:

message

日志信息,一个列表或字符串,被log.msg或log.err传递过来的。

isError

一个布尔值,如果为True时就是从log.err过来的。如果为True说明字典当中还会有个Failure对象。

其他项目是自动被加入的:

printed

这条信息是从sys.stdout中捕获的,例如这条信息是从print输出的。如果isError同样为True,则是从sys.stderr来的。

你可以通过 log.msg 或 log.err 传递附加项目到事件字典。标准的日志监视器将会忽略他们不用的字典参数。

Note

注意

  • 不要在日志监视器中抛出异常,否则会挂掉。
  • 不要在日志监视器中阻塞,尤其是在主线程中。这将会导致很多问题。
  • 日志监视器需要线程安全。

Erlang超快速入门

Monday, September 10th, 2007

Erlang超快速入门

日期: 2007-04-06

目录

1 开始使用erlang

如果你在unix系统下输入 erl ,或者在Window$系统下双击Erlang的图标,你可以看到一些提示:

os prompt > erl
Eshell V5.5.4  (abort with ^G)
1> _

其中 “>”提示符意味着系统正在等待输入。

2 使用Erlang作为计算器

1> 213183682167*12937192739173917823.
27579983733990928813319999135233
2> _

记住每个表达式以英文句号结束

3 编辑前面的表达式

可以使用简单的emacs命令获取前面的表达式。常见的几个如下:

Unix键 Win$键 说明
^P Up 获取前一行(previous)
^N Down 获取下一行(next)
^A Home 到行首
^E End 到行尾
^D Del 删除光标前字符
^F Left 向前移动一个字符
^B Right 向后移动一个字符
Return Enter 执行当前命令

注意:^X意味着Control+X 。

尝试按下Control+P来查看结果。

译者注:一位朋友提示如上的快捷键是在unix系统之下的,Window$下的快捷键附在了如上列表后的括号内。另外,在Unix系统下使用Control+G的退出方式,在Window$下使用Control+C来退出。

4 编译你的第一个程序

把如下内容输入到一个文件里:

-module(test).
-export([fac/1]).

fac(0) -> 1;
fac(N) -> N * fac(N-1).

把这些存储到文件 test.erl 中,文件名必须与模块名相同。

编译这个程序使用如下命令,并且运行:

3> c(test).
{ok,test}
30> test:fac(20).
2432902008176640000
4> test:fac(40).
815915283247897734345611269596115894272000000000
32> _

现在可以做些其他有趣的事情了。

5 深入了解Erlang

Getting Started

Warp me to the documentation – I want to read it all

memcached简介

Monday, September 10th, 2007

memcached简介

翻译: gashero

目录

主要取自 memcached 的主页。

memcached的Python客户端下载地址为 python-memcached

1   什么是memcached

memcached是一个高性能的、分布式内存对象缓存系统,尽管很通用,但是用来加速WEB应用、降低数据库负载时比较多。

Danga Interactive 开发了memcached用来提高 LiveJournal.com 的速度,这个站点每天处理2000万以上的动态页面请求,独立用户有100万左右。memcached把数据库的负载降到了几乎没什么事可干的地步,并为用户提供很快的页面响应速度,更好的资源利用率和更快的数据库存取操作。

2   它如何工作

首先启动一个memcached监护进程,监护进程不需要配置文件,只要在命令行里面加三四个参数就可以了:

# ./memcached -d -m 2048 -l 10.0.0.40 -p 11211

这里指定使用2GB内存,监听10.0.0.40的11211端口来启动memcached。因为32位处理器最多只能有4GB的地址空间。如果你确实需要32位的服务器处理4-64GB的内存(使用PAE),则每个使用2-3GB内存。

3   移植应用

Warning

pass

4   数据库不能做这些么

数据库为了在关系数据库管理系统上实现ACID,使用了锁,导致很多读写阻塞。而memcached从不会阻塞。

5   那共享内存呢

首先使用缓存的很多用户都是用于WEB。这意味着要分配时间出来做多请求处理给不同的系统,如mod_perl、PHP等等,这是对全局缓存的一种浪费。如果你使用多线程语言和共享内存API,如IPC::Shareable,你的每个线程都拥有独立的缓存。这个也是共享内存的局限性。

memcached服务器和客户端实现了全局缓存。实际上,很推荐你在同一台机器上同时运行WEB结点和memcached结点。这会节省一点网络开销。

6   MySQL 4.x的查询缓存如何

Warning

pass

7   数据库复制的对比

Warning

pass

8   memcached够快么

非常快,它使用 libevent 来处理任意数量的连接。在Linux上,尽可能使用 epoll 来实现。使用非阻塞的网络IO和计算,所以对象在不同的客户端上可能有不同的状态。还使用内置的内存块分配和哈希表算法,确保虚拟内存不会过来捣乱。

9   竞争情况

你可能感兴趣用户是否会获取一个过期的值。

服务器API只有一种获取数据的方法,并有3种提交数据的方法:

  • set 无条件的设置指定的键为给定的值。
  • add 添加到缓存,如果不存在。
  • replace 当存在已有值时,替换一下。

另外,这3个函数都可以设置过期时间。

10   memcached的Python客户端

10.1   基本使用

import memcache
mc=memcache.Client(['127.0.0.1:11211'],debug=0)
mc.set("some_key","Some value")
value=mc.get("some_key")
mc.set("another_key",3)
mc.delete("another_key)
mc.set("key","1") #用于自动增量/减量的必须是字符串
mc.incr("key")
mc.decr("key")

标准的使用memcache作为数据库缓存的方法如下:

key=derive_key(obj)
obj=mc.get(key)
if not obj:
    obj=backend_api.get(...)
    mc.set(obj)
#现在可以操作obj了

10.2   Client类

__init__(servers,debug=0,pickleProtocol=0,pickler=pickle.Pickler,unpickler=pickle.Unpickler)

构造函数

delete(key,time=0)

删除某个键。time的单位是秒,确保特定时间内的set/update操作会失败。返回1成功,0失败。

incr(key,delta=1)

给自增量变量加上delta,默认为1。

decr(key,delta=1)

给自减量变量减去delta,默认为1。

add(key,val,time=0,min_compress_len=0)

添加一个键值对,内部调用_set()方法。

replace(key,val,time=0,min_compress_len=0)

替换值,内部调用_set()方法。

set(key,val,time=0,min_compress_len=0)

无条件的设置键值对。time设置超时,单位是秒。min_compress_len用于设置zlib压缩。内部调用_set()方法。

set_multi(mapping,time=0,key_prefix=”,min_compress_len=0)

设置多个键值对。

get(key)

获取值。出错则返回None。

get_multi(keys,key_prefix=”)

获取多个键的值,返回字典。keys为健明列表。key_prefix是键名前缀,可以最终构成key_prefix+key的完整键名。与set_multi中一样。

江湖功夫,无坚不破,唯快不破

Friday, September 7th, 2007

江湖功夫,无坚不破,唯快不破

这是周星驰的电影《功夫》里面火云邪神的一句话,我很欣赏。虽然邪神说出这句时的字面意思很浅显,但是仔细想想其实引申义很有道理。说白了就是一个快字。

现实中可以以这个快来引导成功的还有很多事情。一个广义的快可以引发很多改变。而对做coder的我来说,无非就是快速的学习,快速的进步。对coder这个职业来说,无论你知识积累有多么深厚,如果不能保持快速的学习,也就早晚被一个更快的人“无坚不破”了。

小时候,父亲教导过我即便别人求助也不要教会别人什么,典型的一句古话就是“教会徒弟,饿死师傅”。不过,最终我进入了一个高速变化快速淘汰的行业,这也就没有太多的意义了。所以这么多年以来,无论中学大学,以致现在工作,我对自己掌握的东西基本没什么隐藏,大多是公布出来的。其实别人会了什么并没有什么要紧,关键是一个对比。这里又想起另外一个故事,林中二人遇虎,一人立即去换跑鞋,另外一人问:“你换了鞋就可以跑过老虎么?”,那人回答:“只要跑的过你就可以了”。这个故事真的太经典了。我公开了这么多我会的又有什么关系,只要保持我继续快速的学习,我就总是领先的。而我公开的这些知识又可以对他人有所帮助,所谓助人为乐。

人在江湖,面对竞争,甚至淘汰是常有的事情。面对眼前的对手总要有个策略,而快无疑是个很好的策略。其实对各种对手来说,我比较敬畏的是年龄比我小而成就比我高的,因为这类对手才是比我更快的。如果眼前的对手年龄已经很大了,或者至少是毕业时间、工作年限等等比自己高,那么自己还有机会,谁能说未来会是什么样子。至于年纪大的对手在自己同样年龄时尚且不如自己的,那么赶超不过是一点点时间问题。

有时我甚至还暗暗庆幸自己来到了这样一个行业,一个高速发展快速淘汰的行业。进入这个行业的人都知道,很难有人在这个行业做一辈子,总归要转行的,但是到了转行的时候就很不同了。当你已经习惯了快,面对固步自封的对手,是何其容易。

到写这篇小文时,我学习使用reStructuredText格式来制作电子笔记已经6个月零5天了,制作的reST源文件共有770KB。折合汉字接近40万字,当然,里面有些代码是英文的。不过这也没有包括工作中写的各类报告,仅仅是笔记而已。其实,很多人都可以比这个速度快很多倍的,唯一不同的是,并不是太多人像我一样每天都花很多时间来看书。热血终归要有,但是如果不能持续,那么效果就很有限了。

另外,看书也是一个很好的例子,我在整个学习的阶段,总是有朋友和同学来问我,想要学某某技术看哪一本书最好。这个问题我是很厌于回答的,何必呢,你对一种技术的了解程度不在于你看的唯一一本书到底有多么经典。而是在于你到底看了多少本书。每每面对一门新技术时,我基本上都是不选书的,而是随机选一本,然后读下来,之后再去读下一本。一路下来,加上恰当的笔记方法,对一门技术的了解就可以很深入了。

当然,不仅仅是这个行业,其实很多其他行业也可以引入“快”这个观念来战胜对手。一个职员如果工作的速度比别人快,那么距离他晋升也不会远了。如果一个企业,改造产品,对用户需求随需应变的速度快,那么对手也不过是陪衬而已。

广义的快还包括领先,比如技术领先、比如利润率领先。大家所熟知的CPU行业,竞争N多年来,Intel的对手依次进入,依次倒下。仅仅有与Intel有很像的风格的AMD可以坚持下来就可以看到了。CPU虽然属于集成电路的一种,但是与集成电路行业却有着天壤之别。传统的集成电路厂商出个TDAxxxx的东西可以拿出去卖二三十年还畅销不衰,这里可以看到固步自封的影子。而CPU行业则不同,摩尔定律的出现预示着这个行业与集成电路行业的完全不同。高速发展快速淘汰之后,那些老牌集成电路厂商就败下阵来。Motorola在通信和集成电路领域的地位自不必说,而他也曾经生产过一些CPU,不过后来因为行业习惯,发展和更新的速度远不如Intel,也就慢慢退出了计算机CPU市场,现在的Motorola的芯片只能用在一些工业控制领域。几经淘汰之后,能保持快速发展的也就最终生存了下来,也就是现在的Intel和AMD。

好了说点实用的吧,我所追求的快,是指学习的快。所以也在笔记方法和学习方法方面略有探索。我用reStructuredText所做的第一份笔记叫做《开动大脑》,讲思维导图的一些东西。虽然后来一直想利用思维导图的方式提高学习效率,但是终归效果不够理想。免费的思维导图软件只有FreeMind一种,但是那种图根本算不上思维导图,烂的很,况且又是依赖JRE的,所以坚持手工画了数十页思维导图也没有用FreeMind。当然,思维导图在我分析pynids的模块关系时起到了很大的作用。最近倒是遇到了另外一种可以画思维导图的工具,叫做“graphviz”,现在正在学习其dotguide,并寄希望于它可以把我基于reST的重量级学习过程转化成轻量级的学习过程。

好了,祝大家都可以“快”乐吧。

在Windows下用MinGW编译Python扩展模块

Thursday, September 6th, 2007

在Windows下用MinGW编译Python扩展模块

作者: gashero

测试使用VC++ 2003 Toolkit并不顺利,所以先用这个MinGW,似乎还不错。

MinGW的安装

我的是gcc 3.2.3的解压缩包,解压后设置PATH就可以用了。

构建

可以直接在构建命令上指定编译器来运行:

python setup.py build -c mingw32
python setup.py build -compiler=mingw32

两种命令都相同。编译完成后可以在 \build\lib.win32-2.4 看到得到的*.pyd文件。把这个文件复制到site-packages就可以用了。

还可以在setup.py所在目录下新建一个setup.cfg文件,指定默认的编译器为MinGW。同时在构建Windows二进制安装包时也需要用这种方法来指定默认编译器。内容如下:

[build]
compiler=mingw32

制作二进制安装包

如上方法指定默认编译器为MinGW之后,使用命令:

python setup.py bdist_wininst

来生成Windows二进制安装包,新生成的二进制安装包在 dist\ 目录下。

一般来说这个安装包在各类Windows系统上双击安装,一路Next下去就可以了。最终会在Python安装目录生成一个卸载用的可执行文件,并且把软件包安装到site-packages目录下。

SQLAlchemy指南(tutorial)

Thursday, September 6th, 2007

SQLAlchemy指南(tutorial)

对应版本: 0.3.4

目录

这个入门指导用于SQLAlchemy的快速入门,并便利SQLAlchemy的简单功能。如果你可以跳过这一部分进入 主文档 会涉及更多内容。如下的例子全部是在Python交互模式下完成了,并且全部通过了 doctest 测试。

1 安装

1.1 安装SQLAlchemy

setuptools 安装是非常简单的,只要运行如下命令即可:

# easy_install SQLAlchemy

这将会在Python Cheese Shop获取SQLAlchemy的最新版本并安装。或者你也可以使用setup.py安装一个发行包:

# python setup.py install

1.2 安装一个数据库API

SQLAlchemy被设计用于操作一个 DBAPI 实现,包括大多数常见的数据库。如果你有一个支持DBAPI的实现,那么可以跳入下一节。另外SQLite是一个易于使用的数据库,可以快速开始,并且他可以使用内存数据库。如果要使用SQLite,你将会需要:

  • pysqlite – SQLite的Python接口
  • SQLite函数库

注意在Windows下并不需要SQLite函数库,因为Windows版的pysqlite已经内含了。pysqlite和SQLite可以被安装到Linux或FreeBSD,通过预编译或从源码安装。

预编译包的地址为:

http://initd.org/tracker/pysqlite/wiki/PysqlitePackages

2 快速开始

2.1 导入

SQLAlchemy提供了完整的命名空间,只要导入sqlalchemy即可,无需其子包。为了方便使用本教程,我们导入所有命名到本地命名空间:

>>> from sqlalchemy import *

2.2 连接到数据库

导入之后,下一步是连接到需要的数据库,表现为(represent)为一个Engine对象。这个对象处理了连接的管理和特定数据库的操作。下面,我们连接SQLite基于文件的数据库”tutorial.db”

>>> db=create_engine("sqlite:///tutorial.db")

创建数据库引擎的更多信息查看”Database Engines”。

3 SQLAlchemy是两个库的包装

现在已经完成了安装和连接数据库,可以开始做点实际的事情了。但首先需要有些解释。

SQLAlchemy的核心有两个完全不同的功能,一个在另一个之上工作。一个是 SQL语言构造器 ,另一个是 ORM 。SQL语言构造器允许调用 ClauseElements 来构造SQL表达式。这些 ClauseElements 可以在编译成字符串并绑定到数据库后用于执行,并返回一个叫做 ResultProxy 的对象,类似于一个结果集对象,但是更象dbapi高版本的 cursor 对象。

ORM是建立在SQL语言构造器之上的工具集,用于将Python对象映射到数据库的行,提供了一系列接口用于从数据库中存取对象(行)。在ORM工作时,在底层调用SQL语言构造器的API,这些通用的操作有些许的不同。不同的是,你不再使用行,而是使用自定义类的对象来操作。另外,数据库的查询方式也不同,ORM的可以生成大多数的SQL查询,除此之外还可以在类中定义更多操作。

SA功能强大,无与伦比,只是有两个混合在一起的方法有些复杂。有效的使用SA的方法是先了解这两种不同的工具集,这是两个不同的概念,而大家常常混交SQL语言构造器和ORM。关键的不同是,使用cursor形式的结果集时使用的是SQL语言构造器;而使用类实例进行管理时使用的是ORM。

本指南首先介绍SQL语言构造器,首先需要声明的数据库信息叫做 table metadata 。本指南包含了一些SQL构造的例子,包括如何有效的使用SQL语言构造器的例子。

4 操作数据库对象

在SQLAlchemy的核心哲学中表格和类是不同的。因为如此,SQLAlchemy提供了构造表格的方法(使用表格的元信息table metadata)。所以我们从构造表格的元信息对象和定制操作他们的对象开始。稍后我们可以看到SQLAlchemy的ORM,提供了表格元信息的高层封装,允许我们随心所欲的装载和保存Python类。

4.1 定义元信息,绑定到引擎

首先,你的表格必须已经在MetaData集合中。我们将要创建简单(handy)表格的MetaData,并自动连接到引擎(将一个模式(schema)对象连接到引擎成为绑定binding):

>>> metadata=BoundMetaData(db)

一个构造BoundMetaData对象的等同方法是直接使用引擎URL,这将会帮我们调用 create_engine

>>> metadata=BoundMetaData("sqlite:///tutorial.db")

现在,我们告知metadata关于数据库中的表格,我们可以使用(issue)CREATE语句来创建表格,并且通过他们来创建和执行SQL语句,除非需要打开和关闭任何连接。这都是自动完成的。注意这个功能是推荐使用的。SQLAlchemy包含了使用模式进行连接管理和SQL构造的全部功能,并可以在任何引擎上进行操作。

本教程的目的,是教会大家使用”bound”对象,他可以使得代码简单和易读。

4.2 创建表格

使用metadata作为基本连接,我们可以创建表格:

>>> users_table=Table('user',metadata,
...     Column('user_id',Integer,primary_key=True),
...     Column('user_name',String(40)),
...     Column('password',String(10))
... )

有如你看到的,我们刚刚定义了一个叫做users的表格并拥有3个列:user_id作为主键,user_name和password。它现在只是一个对象而与数据库中的表格没有必然联系。为了让表格生效,我们使用create()方法。有趣的是,我们可以让SQLAlchemy发送SQL语句到数据库时同时显示SQL语句,只要设置BoundMetaData关联的Engine的echo选项即可:

>>> metadata.engine.echo=True
>>> users_table.create()
CREATE TABLE users (
    user_id INTEGER NOT NULL,
    user_name VARCHAR(40),
    password VARCHAR(10),
    PRIMARY KEY (user_id)
)
...

或者,如果users表格已经存在了(比如你第二次运行这些例子),在这种情况下你可以跳过create()方法的调用。你设置可以跳过列定义,而是让SQLAlchemy自动从数据库装入定义:

>>> users_table=Table('users',metadata,autoload=True)
>>> list(users_table.columns)[0].name
'user_id'

关于表格元信息的文档在”Database Meda Data”中。

4.3 插入记录

插入记录是通过表格对象的insert()方法实现的,这将会定义一个子句对象(clause object)(就是CluseElement)来代理INSERT语句:

>>> i=users_table.insert()
>>> i
<sqlalchemy.sql._Insert object at 0x...>
>>> print i
INSERT INTO users (user_id,user_name,password) VALUES (?,?,?)

当我们创建这个插入语句对象时,语句本身也绑定到了Engine,并已经可以执行了。子句对象的execute()方法将会将对象编译为特定引擎的SQL方言,并且执行语句:

>>> i.execute(user_name='Mary',password='secure')
INSERT INTO users (user_name,password) VALUES (?,?)
['Mary','secure']
COMMIT
<sqlalchemy.engine.base.ResultProxy object at 0x...>
>>> i.execute({'user_name':'Tom'},{'user_name':'Fred'},{'user_name':'Harry'})
INSERT INTO users (user_name) VALUES (?)
[['Tom'],['Fred'],['Harry']]
COMMIT
<sqlalchemy.engine.base.ResultProxy object at 0x...>

注意VALUES子句会自动调整参数数量。这是因为ClauseElement的编译步骤并不依赖于特定的数据库,执行的参数也是如此。

当构造子句对象时,SQLAlchemy会绑定所有的值到参数。在构造时,参数绑定总是依靠键值对。在编译时,SQLAlchemy会转换他们到适当的格式,基于DBAPI的参数风格。这在DBAPI中描述的参数位置绑定中同样工作的很好。

这些文档继承于”Inserts”。

4.4 查询

我们可以检查users表中已经存在的数据。方法同插入的例子,只是你需要调用表格的select()方法:

>>> s=users_table.select()
>>> print s
SELECT users.user_id,users.user_name,users.password
FROM users
>>> r=s.execute()
SELECT users.user_id,users.user_name,users.password
FROM users
[]

这时,我们并没有忽略execute()的返回值。他是一个ResultProxy实例,保存了结果,而行为非常类似于DBAPI中的cursor对象:

>>> r
<sqlalchemy.engine.base.ResultProxy object at 0x...>
>>> r.fetchone()
(1,u'Mary',u'secure')
>>> r.fetchall()
[(2,u'Tom',None),(3,u'Fred',None),(4,u'Harry',None)]

查询条件同Python表达式,使用Column对象。所有表达式中的Column对象都是ClauseElements的实例,例如Select、Insert和Table对象本身:

>>> r=users_table.select(users_table.c.user_name=='Harry').execute()
SELECT users.user_id,users.user_name,users.password
FROM users
WHERE users.user_name=?
['Harry']
>>> row=r.fetchone()
>>> print row
(4,u'Harry',None)

所幸的是所有标准SQL操作都可以用Python表达式来构造,包括连接(join)、排序(order)、分组(group)、函数(function)、子查询(correlated subquery)、联合(union)等等。关于查询的文档”Simple Select”。

4.5 操作记录

你可以看到,当我们打印记录时返回的可执行对象,它以元组打印记录。这些记录实际上同时支持列表(list)和字典(dict)接口。字典接口允许通过字符串的列名定位字段,或者通过Column对象:

>>> row.keys()
['user_id','user_name','password']
>>> row['user_id'],row[1],row[users_table.c.password]
(4,u'Harry',None)

通过Column对象来定位是很方便的,因为这样避免了使用列名的方式。

结果集也是支持序列操作的。但是相对于select还有微小的差别,就是允许指定所选的列:

>>> for row in select([user_table.c.user_id,users_table.c.user_name]).execute():
...     print row
SELECT users.user_id,users.user_name
FROM users
[]
(1,u'Mary')
... ...

4.6 表间关系

我们可以创建第二个表格,email_addresses,这会引用users表。定义表间的关联,使用ForeignKey构造。我们将来也会考虑使用表格的CREATE语句:

>>> email_addresses_table=Table('email_addresses',metadata,
...     Column('address_id',Integer,primary_key=True),
...     Column('email_address',String(100),nullable=False),
...     Column('user_id',Integer,ForeignKey('users.user_id')))
>>> email_addresses_table.create()
CREATE TABLE email_addresses (
    address_id INTEGER NOT NULL,
    email_address VARCHAR(100) NOT NULL,
    user_id INTEGER,
    PRIMARY KEY (address_id),
    FOREIGN KEY(user_id) REFERENCES users (user_id)
)
...

上面的email_addresses表与表users通过ForeignKey(‘users.user_id’)相联系。ForeignKey的构造器需要一个Column对象,或一个字符串代表表明和列名。当使用了字符串参数时,引用的表必须已经存在于相同的MetaData对象中,当然,可以是另外一个表。

下面可以尝试插入数据:

>>> email_addresses_table.insert().execute(
...     {'email_address':'tom@tom.com','user_id':2},
...     {'email_address':'mary@mary.com','user_id':1})
INSERT INTO email_addresses (email_address,user_id) VALUES (?,?)
[['tom@tom.com',2],['mary@mary.com',1]]
COMMIT
<sqlalchemy.engine.base.ResultProxy object at 0x...>

在两个表之间,我们可以使用 join 方法构造一个连接:

>>> r=users_table.join(email_addresses_table).select().execute()
SELECT users.user_id, users.user_name, users.password,
email_addresses.address_id, email_addresses.email_address,
email_addresses.user_id
FROM users JOIN email_addresses ON users.user_id=email_addresses.user_id
[]
>>> print [row for row in r]
[(1, u'Mary', u'secure', 2, u'mary@mary.com', 1),
(2,u'Tom', None, 1, u'tom@tom.com', 2)]

join 方法同时也是 sqlalchemy 命名空间中的一个独立的函数。连接条件指明了表对象给定的外键。条件(condition),也可以叫做 “ON 子句” ,可以被明确的指定,例如这个例子我们查询所有使用邮件地址作为密码的用户:

>>> print join(users_table, email_addresses_table,
...     and_(users_table.c.user_id==email_addresses_table.c.user_id,
...     users_table.c.password==email_addresses_table.c.email_address)
...     )
users JOIN email_addresses ON users.user_id=email_addresses.user_id AND
users.password=email_address.email_address

5 使用ORM工作

现在我们已经有了一些表格和SQL操作的知识了,让我们来看看SQLAlchemy的ORM (Object Relational Mapper) 。使用ORM,你可以将表格(和其他可以查询的对象)同Python联系起来,放入映射集(Mappers)当中。然后你可以执行查询并返回 对象实例 列表,而不是结果集。 对象实例 也被联系到一个叫做 Session 的对象,确保自动跟踪对象的改变,并可以使用 flush 立即保存结果。

5.1 创建一个映射

一个映射通常对应一个Python类,其核心意图是,“这个类的对象是用作这个表格的行来存储的”。让我们创建一个类叫做 User ,描述了一个用户对象,并保存到 users 表格。:

>>> class User(object):
...     def __repr__(self):
...         return "%s(%r,%r)"%(
...             self.__class__.__name__,self.user_name,self.password)

这个类是一个新形式(new style)的类(继承自 object )并且不需要构造器(在需要时默认提供)。我们只实现了一个 __repr__ 方法,用于显示 User 对象的基本信息。注意 __repr__ 方法应用了实例变量 user_namepassword ,这是还没定义的。我们可选的定义这些属性,并可以进行处理;SQLAlchemy的 Mapper 的构造器会自动管理这些,而且会自动协调到 users 表格的列名。让我们创建映射,并观察这些属性的定义:

>>> usermapper=mapper(User,users_table)
>>> ul=User()
>>> print ul.user_name
None
>>> print ul.password
None

函数 mapper 返回新建的 Mapper 实例。这也是我们为 User 类创建的第一个映射,也就是类的 主映射 。一般来说,不需要保存 usermapper 变量;SA的ORM会自动管理这个映射。

5.2 获取会话(Session)

创建了一个映射之后,所有对映射的操作都需要一个重要的对象叫做 Session 。所有对象通过映射的载入和保存都 必须 通过 Session 对象,有如对象的工作空间一样被加载到内存。特定对象在特定时间只能关联到一个 Session

缺省时,需要在载入和保存对象之前明确的创建 Session 对象。有多种方法来管理会话,但最简明的方法是调用 create_session()

>>> session=create_session()
>>> session
<sqlalchemy.orm.session.Session object at 0x...>

5.3 查询对象

会话对象拥有载入和存储对象的所有方法,同时也可以查看他们的状态。会话还提供了查询数据库的方便接口,你可以获取一个 Query 对象:

>>> query=session.query(User)
>>> print query.select_by(user_name='Harry')
SELECT users.user_name AS users_user_name, users.password AS users_password,
users.user_id AS users_user_id
FROM users
WHERE users.user_name=? ORDER BY users.oid
['Harry']
[User(u'Harry',None)]

对象所有的查询操作实际上都是通过 Query 的。 Mapper 对象的多种 select 方法也是偷偷的在使用 Query 对象来执行操作。一个 Query 总是联系到一个特定的会话上。

让我们暂时关闭数据库回显,并尝试 Query 的几个方法。结尾是 _by 的方法主要用于对象的键参数。其他的方法允许接受 ClauseElement 对象,使用 Column 对象的Python表达式产生,同样的方法我们在上一节使用过。使用 ClauseElement 结构来查询更加冗长,但是更加灵活:

>>> metadata.engine.echo=False
>>> print query.select(User.c.user_id==3)
[User(u'Fred',None)]
>>> print query.get(2)
User(u'Tom',None)
>>> print query.get_by(user_name='Mary')
User(u'Mary',u'secure')
>>> print query.selectfirst(User.c.password==None)
User(u'Tom',None)
>>> print query.count()
4

Note

User类有一个特别的属性 c ,这个属性描述了User映射表格对象的列。

User.c.user_name 等同于 users_table.c.user_name ,记得 User 是Python对象,而 usersTable 对象。

5.4 修改数据

作为小经验,我们看看如何做出修改。首先,创建一个新的用户”Ed”,然后加入会话:

>>> ed=User()
>>> ed.user_name='Ed'
>>> ed.password='edspassword'
>>> session.save(ed)
>>> ed in session
True

也可以修改数据库中的其他对象。使用 Query 对象载入,然后改变:

>>> mary=query.get_by(user_name='Mary')
>>> harry=query.get_by(user_name='Harry')
>>> mary.password='marysnewpassword'
>>> harry.password='harrysnewpassword'

这时,什么东西都没有保存到数据库;我们所有的修改都在内存中。如果这时程序其他部分尝试修改’Mary’会发生什么呢?因为使用相同的会话,所以再次载入’Mary’实际上定位到相同的主键’Mary’,并且 返回同一对象实例 。这个行为用在会话中确保数据库的一致性:

>>> mary2=query.get_by(user_name='Mary')
>>> mary is mary2
True

在唯一映射中,单一的会话可以确保安全的操作对象。

如果两个不同的会话同时操作一个对象,会检测到并发;SA会使用简单的并发控制来保存对象,可以选择使用拥有更强的使用ids的检查。参考 Mapper Arguments 了解更多细节。

5.5 保存

在新建了用户”ed”并对”Mary”和”Harry”作修改之后,我们再删除”Fred”

>>> fred=query.get_by(user_name='Fred')
>>> session.delete(fred)

然后发送更改到数据库,使用会话的 flush() 方法。开启回显来查看过程:

>>> metadata.engine.echo=True
>>> session.flush()
BEGIN
UPDATE users SET password=? WHERE users.user_id=?
['marysnewpassword',1]
UPDATE users SET password=? WHERE users.user_id=?
['harrysnewpassword',4]
INSERT INTO users (user_name,password) VALUES (?,?)
['Ed','edspassword']
DELETE FROM users WHERE users.user_id=?
[3]
COMMIT

5.6 关系

如果一个关系包含其他信息时,例如包含邮件地址的列表,我们可以在使用 relation() 创建 Mapper 时声明。当然,你还可以对这个关系作很多事情,我们举几个简单的例子。首先使用 users 表,它拥有一个外键关系连接到 email_addresses 表。 email_addresses 表中的每一行都有列 user_id 用来引用 users 表中的一行;而且 email_addresses 表中的多行可以引用 users 表中的同一行,这叫一对多关系。

首先,处理 email_addresses 表。我们创建一个新的类 Address 描述了 email_addresses 表中的一行,并且也创建了用于 Address 类的映射对象:

>>> class Address(object):
...     def __init__(self,email_address):
...         self.email_address=email_address
...     def __repr__(self):
...         return "%s(%r)"%(
...             self.__class__.__name__,self.email_address)
>>> mapper(Address, email_addresses_table)
<sqlalchemy.orm.mapper.Mapper object at 0x...>

然后,我们通过使用 relation() 创建一个关系连接 UserAddress 类,并且添加关系到 User 映射,使用 add_property 函数:

>>> usermapper.add_property('addresses',relation(Address))

函数 relation() 需要一个类或映射作为首参数,并且还有很多选项来控制行为。 User 映射现在给每一个 User 实例添加了一个属性叫 addresses 。SA将会自动检测这个一对多关系。并且随后创建 addresses 列表。当新的 User 对象创建时,这个列表为空 。

让我们看看数据库做了什么。当我们修改映射的配置时,最好清理一下会话,让所有载入的 User 对象可以重新载入:

>>> session.clear()

我们随之可以使用 User 对象的 addresses 属性来象列表一样处理:

>>> mary=query.get_by(user_name='Mary')
SELECT users.user_name AS users_user_name, users.password AS users_password,
users.user_id AS users_user_id
FROM users
WHERE users.user_name=? ORDER BY users.oid
LIMIT 1 OFFSET 0
['Mary']
>>> print [a for a in mary.address]
SELECT email_addresses.user_id AS email_address_user_id,
email_addresses.address_id AS email_addresses_address_id,
email_addresses.email_address AS email_addresses_email_address
FROM email_addresses
WHERE ?= email_addresses.user_id ORDER BY email_addresses.oid
[1]
[Address(u'mary@mary.com')]

向列表添加元素也很简单。新的 Address 对象将会在调用会话的flush()时保存:

>>> mary.addresses.append(Address('mary2@gmail.com'))
>>> session.flush()
BEGIN
INSERT INTO email_addresses (email_address,user_id) VALUEs (?,?)
['mary2@gmail.com',1]
COMMIT

主文档中关于使用映射的部分在如下地址:

http://www.sqlalchemy.org/docs/datamapping.myt#datamapping

5.7 事务

你可能已经注意到在上面例子中的 session.flush() ,SQLAlchemy使用 BEGINCOMMIT 来使用数据库事务。 flush() 方法使用事务来对一些记录执行一系列的指令。如果希望在 flush() 之外使用更大规模的事务,可以通过 SessionTransaction 对象,其生成使用 session.create_transaction() 。下面将会执行一个非常复杂的 SELECT 语句,进行大量的修改,然后再创建一个有两个邮箱的用户,这些都在事务中完成。而且将会在中间使用 flush() 来保存,然后在执行最后的 commit() 时将所有改变写入数据库。我们把事务封装在一个 try/except 语句块当中确保资源的安全释放:

>>> transaction=session.create_transaction()
>>> try:
...     (ed,harry,mary)=session.query(User).select(
...         User.c.user_name.in_('Ed','Harry','Mary'),
...         order_by=User.c.user_name
...     )
...     del mary.address[1]
...     harry.addresses.append(Address('harry2@gmail.com'))
...     session.flush()
...     print "***flushed the session***"
...     fred=User()
...     fred.user_name='fred_again'
...     fred.addresses.append(Address('fred@fred.com'))
...     fred.addresses.append(Address('fredsnewemail@fred.com'))
...     session.save(fred)
...     transaction.commit()
... except:
...     transaction.rollback()
...     raise
BEGIN
SELECT users.user_name AS users_user_name,
users.password AS users_password,
users.user_id AS users_user_id
FROM users
WHERE users.user_name IN (?, ?, ?) ORDER BY users.user_name
['Ed', 'Harry', 'Mary']
SELECT email_addresses.user_id AS email_addresses_user_id,
email_addresses.address_id AS email_addresses_address_id,
email_addresses.email_address AS email_addresses_email_address
FROM email_addresses
WHERE ? = email_addresses.user_id ORDER BY email_addresses.oid
[4]
UPDATE email_addresses SET user_id=? WHERE email_addresses.address_id = ?
[None, 3]
INSERT INTO email_addresses (email_address, user_id) VALUES (?, ?)
['harry2@gmail.com', 4]
***flushed the session***
INSERT INTO users (user_name, password) VALUES (?, ?)
['fred_again', None]
INSERT INTO email_addresses (email_address, user_id) VALUES (?, ?)
['fred@fred.com', 6]
INSERT INTO email_addresses (email_address, user_id) VALUES (?, ?)
['fredsnewemail@fred.com', 6]
COMMIT

对应的主文档:

http://www.sqlalchemy.org/docs/unitofwork.myt#unitofwork

5.8 下一步

如上已经介绍了一下SQLAlchemy。但是不同的人可能有不同的做事方法,比如定义不同风格的映射的关系,所以还是允许使用原始的SQL来定义表格,还有Engine、SQL语句、数据库连接等。

Berkeley DB 3.x & 4.x Python扩展包

Thursday, September 6th, 2007

Berkeley DB 3.x & 4.x Python扩展包

翻译: gashero <harry.python@gmail.com>

目录

1 简介

这里介绍了一点关于bsddb3.db的Python扩展模块的东西,它包装了Berkeley DB 3.x和4.x的C库。这里的扩展模块指部分纯Python模块。

本模块希望可以应用到如下情况中。这个模块用于确保作事情不要太复杂,而在需要复杂的时候也可以提供相关功能。

  1. 向后兼容。本模块一直希望可以适应各种版本的BDB接口,甚至于1.85接口。这意味着需要创建具备相同接口的,比如btopen()、hashopen()、rnopen()和他们返回的对象接口,特别是first()、last()、next()、prev()这些经常用于避免使用游标的接口。这些都是在 bsddb3.__init__.py 中以Python代码实现的。
  2. 简单的持续字典。在前面实现的前进了一小步。程序员可能希望直接使用新的DB对象,但是可能仅仅在单一进程或线程中使用。这时程序员并不需要被DBEnv所骚扰,应该尽可能的表现的像一个字典。
  3. 并发存取字典。这需要具备处理单一写者和多读者的DB对象,常见于多线程或多进程环境。这时需要使用适当的参数创建DBEnv对象。这时并不需要其他附加的操作。
  4. 高级的基于事务的数据存储。这将会发挥BerkeleyDB的所有能力。程序员在这时可能并非使用字典活其他类似的DB接口,而是传递一个事务对象。另外,这类功能大部分可以简单的通过设置DBEnv的参数来实现,这时可以使用事务,并且在发现死锁时产生异常,等等。

2 提供的类型

bsddb3.db 扩展模块提供如下对象类型:

  • DB :简单的数据库对象,支持Hash/BTree/Recno/Queue的存取方法。
  • DBEnv :提供数据库环境以进行更多高级设置,如事务、日志、并发存取等。
  • DBCursor :一个类似指针的对象,用于遍历数据库。
  • DBTxn :一个数据库事务。支持多文件提交,中断和数据库修改的检查点。
  • DBLock :一个锁的不透明句柄。查看 DBEnv.lock_get()DBEnv.lock_put() 。锁对于数据库中的内容并不重要,但是对多线程或多进程的并发异步访问是很重要的,需要DBEnv的支持。

3 提供的异常

BerkeleyDB的C API使用函数返回码表示错误。而bsddb3.db模块把这些转换成了Python异常,允许使用try-except来处理。

每个错误代码都转换成了一种异常,如下表所示。如果你使用C API文档,也会很容易的关联这些异常。

所有的异常都是继承自DBError异常,如果你希望处理所有异常,可以直接使用DBError。DBNotFoundError在找不到对应的键名时抛出,DBNotFoundError同时也继承自标准的KeyError异常,用来把DB模拟的像一个字典一样。

当抛出任何一个异常时,都会同时包含一个整数值指定错误代码,和一个字符串指定错误信息。

DBError 基类,以下所有都是从这里继承的
DBIncompleteError DB_INCOMPLETE
DBKeyEmptyError DB_KEYEMPTY
DBKeyExistError DB_KEYEXIST
DBLockDeadlockError DB_LOCK_DEADLOCK
DBLockNotGrantedError DB_LOCK_NOTGRANTED
DBNotFoundError DB_NOTFOUND(同时继承自KeyError)
DBOldVersionError DB_OLD_VERSION
DBRunRecoveryError DB_RUNRECOVERY
DBVerifyBadError DB_VERIFY_BAD
DBNoServerError DB_NOSERVER
DBNoServerHomeError DB_NOSERVER_HOME
DBNoServerIDError DB_NOSERVER_ID
DBInvalidArgError EINVAL
DBAccessError EACCESS
DBNoSpaceError ENOSPC
DBNoMemoryError ENOMEM
DBAgainError EAGAIN
DBBusyError EBUSY
DBFileExistsError EEXIST
DBNoSuchFileError ENOENT
DBPermissionsError EPERM

4 其他包模块

  • dbshelve.py :使用Python标准实现的shelve方式存取对象,同时也提供一些高层方法隐藏bdb底层细节。
  • dbtables.py :这个模块是由Gregory Smith实现的一个简单的表格结构。
  • dbutils.py :一个简单的DB接口提供字典的操作方法。
  • dbobj.py :包含DB和DBEnv的子类。
  • dbrecio.py :包含DBRecIO类,允许像读写文件一样存取DB的记录。

5 测试

一个完整的测试套件测试了所有类型的功能,现在使用了 PyUnit 进行自动化测试,已经包含了超过150个测试用例了。

6 参考

查看C API的 在线文档 ,或 这份文档的本地副本 了解更多功能的细节。Python方法的名称与C API中的相似。

开博纪念

Tuesday, September 4th, 2007

从今天开始,在WordPress上面写博客。纪念一下。