用Python写了一个计划任务,定时更新Memcached中一个key值,写的很happy,几分钟搞定。
然后在Java Servlet测试,代码写的也很happy,编译 – 刷新,一气呵成。
然后发现值一直是null,再tail日志看看,异常不断:
com.danga.MemCached.MemCachedClient Mon Jul 20 09:37:04 CST 2009 - ++++ exception thrown while trying to get object from cache for key: test_num com.danga.MemCached.MemCachedClient Mon Jul 20 09:37:04 CST 2009 - 3 com.danga.MemCached.NestedIOException: 3 at com.danga.MemCached.MemCachedClient.get(MemCachedClient.java:1408) at com.danga.MemCached.MemCachedClient.get(MemCachedClient.java:1270)
晕倒,记得以前为了让两个语言实现API读写共享,手动去修改了两个的API包,实现了中文互读写。难不成今儿个还要手动去搞一把?
然后手动试了下:
shell> telnet xxxxxx 11211 get test_num VALUE test_num 4 2 23
经查证VALUE协议返回的是 key flags len \r\n value 这样的格式,大悟:原来flags不一样啊,Java里面对int型赋值以后flags是0,而Python里则不一样,两者序列化的东西不同啊。懒得去 折腾两者序列化有啥不同。来点直接的把。
然后打开Python Memcached API,大概578行_val_to_store_info方法里,可以看到flags部分,是根据变量类型进行定义的,isinstance(val, str) 如果是str则pass。
到这里就简单了,直接在py代码里:mc.set(‘test_num’, str(num))
Java读取OK。
tornado.database模块简单包装了下对MySQL的操作,短小精悍。
无奈源码中无连接池功能,遂加上了一段DBUtils模块功能。
主要修改了reconnect()方法,大致在database.py第86行左右。(tornado 0.2 win版)
原代码如下:
def reconnect(self): """Closes the existing database connection and re-opens it.""" self.close() self._db = MySQLdb.connect(**self._db_args) self._db.autocommit(True)
修改后:
def reconnect(self): """Closes the existing database connection and re-opens it.""" self.close() try: from DBUtils import PooledDB pool_con = PooledDB.PooledDB(creator=MySQLdb, **self._db_args) self._db = pool_con.connection() except: self._db = MySQLdb.connect(**self._db_args) self._db.autocommit(True)
至于安装DBUtils模块可以去http://pypi.python.org/pypi/DBUtils/下载,也可以简单的用easy_install:
easy_install -U DBUtils
PooledDB有这么几个参数:
* creator 可以生成 DB-API 2 连接的任何函数或 DB-API 2 兼容的数据库连接模块。 * mincached 启动时开启的空连接数量(缺省值 0 意味着开始时不创建连接) * maxcached 连接池使用的最多连接数量(缺省值 0 代表不限制连接池大小) * maxshared 最大允许的共享连接数量(缺省值 0 代表所有连接都是专用的) * maxconnections 最大允许连接数量(缺省值 0 代表不限制) * blocking 设置在达到最大数量时的行为(缺省值 0 或 False) * maxusage 单个连接的最大允许复用次数(缺省值 0 或 False 代表不限制的复用) * setsession: 一个可选的SQL命令列表用于准备每个会话,如 ["set datestyle to german", ...]
creator 函数或可以生成连接的函数可以接受这里传入的其他参数,例如主机名、数据库、用户名、密码等。你还可以选择传入creator函数的其他参数,允许失败重连和负载均衡。
具体可以参照下:http://www.webwareforpython.org/DBUtils/Docs/UsersGuide.zh.html
这是一篇关于pysvn模块的指南.
完整和详细的API请参考 pysvn Programmer’s Reference.
pysvn是操作Subversion版本控制的Python接口模块. 这个API接口可以管理一个工作副本, 查询档案库, 和同步两个.
该API不能创建新的仓库; 只能作用在现有仓库上. 如果你需要创建一个仓库, 请使用Subversion的svnadmin命令.
使用这个API, 你可以check out一份工作拷贝, 添加, 编辑, 和删除工作文件, 和check in, 比较, 或者放弃更改. 仓库属性, 如关键字扩展, 行字符结束, 或者忽略的列表也可以检查和控制.
Subversion 模型
Subversion是一个更新-编辑-提交的模型. 首先在本地建立一个工作副本. 在工作副本上进行修改, 最后提交到中央仓库 (可以是本地或者远程).
这个模型允许多人偶尔会同时修改同一个文件. 大多情况下. Subversion不会干预合并这些不同修改, 如果一个提交失败, 用户或者应用则要重新检查和修改然后再次提交.
常见任务
本节给出一些使用pysvn接口的常用例子. 业务可以会递归的处理目录. 添加参数recurse=False以防止这种行为; 例如, 你可以需要添加内容没有增加一个目录.
check out一份工作副本
import pysvn client = pysvn.Client() #check out the current version of the pysvn project client.checkout('http://localhost/example/trunk', './examples/pysvn') #check out revision 11 of the pysvn project client.checkout('http://localhost/example/trunk', './examples/pysvn-11', revision=pysvn.Revision(pysvn.opt_revision_kind.number, 11))
这是一个建立example测试项目的例子,目录是examples/pysvn. 这个项目是用在剩下的例子.
添加一个文件或者目录到仓库
import pysvn # write a file foo.txt f = file('./examples/pysvn/foo.txt', 'w') f.write('Sample versioned file via pithon\n') f.close() client = pysvn.Client() #schedule the addition; # the working copy will now track the file as a scheduled change client.add('./examples/pysvn/foo.txt') #committing the change actually adds the file to the repository client.checkin(['./examples/pysvn/foo.txt'], 'Adding a sample file')
这个例子是在工作副本中创建了’foo.txt’文件, 然后添加到仓库. 请注意Client.import_()命令会同时增加和提交. 大多数应用, 会在许多修改后再提交.
更新工作副本
import pysvn client = pysvn.Client() client.update('./examples/pysvn')
从仓库中更新其他用户修改并保存到本地副本. 大多数应用应该经常这样做以防止冲突.
提交更新到仓库
import pysvn # edit the file foo.txt f = open('./examples/pysvn/foo.txt', 'w') f.write('Sample versioned file via python\n') f.close() # checkin the change with a log message client = pysvn.Client() client.checkin(['./examples/pysvn'], 'Corrected spelling of python in foo.txt')
提交到Subversion是原子的. 要么所有修改都成功提交, 要么提交失败. 大部分应用会提交工作副本所有修改, 如本例所示, 或者通过个别文件或者目录, 但必须是同一单位.
放弃工作副本修改
import pysvn # edit the file foo.txt f = file('./examples/pysvn/foo.txt', 'w') f.write('This change will never be seen\n') f.close() #discard the edits client = pysvn.Client() client.revert('./examples/pysvn/foo.txt')
这丢弃在工作拷贝和恢复的文件或目录的任何未提交的未经编辑的状态变化.
正在计划增加或移除留无版本或恢复到工作拷贝.
重命名或者移动文件
import pysvn client = pysvn.Client() #rename the file client side client.move('./examples/pysvn/foo.txt', './examples/pysvn/foo2.txt') #checkin the change removes the file from the repository client.checkin(['./examples/pysvn/foo.txt', './examples/pysvn/foo2.txt'], 'Foo has become Foo2')
移动或重命名文件删除旧路径或名称的文件, 并增加了在新的位置, 同时保留以前的版本有关的信息.
在这个例子里, 我们通过文件名Client.checkin()传递父目录也将是有效的.
转移和合并可以在服务器端单步完成; 可以参见仓库任务的那节例子.
从仓库中删除文件或目录
import pysvn client = pysvn.Client() #schedule the removal; # the file will be removed from the working copy client.remove('./examples/pysvn/foo2.txt') #committing the change removes the file from the repository client.checkin(['./examples/pysvn/foo2.txt'], 'Removing sample file')
有些人把删除的文件, 或是用完全清除存储库目录. 该文件仍然存在于以前的版本, 可以通过检查或以其他方式进行审查以前修订的内容检索.
确定等待变动
import pysvn client = pysvn.Client() changes = client.status('./examples/pysvn') print 'files to be added:' print [f.path for f in changes if f.text_status == pysvn.wc_status_kind.added] print 'files to be removed:' print [f.path for f in changes if f.text_status == pysvn.wc_status_kind.deleted] print 'files that have changed:' print [f.path for f in changes if f.text_status == pysvn.wc_status_kind.modified] print 'files with merge conflicts:' print [f.path for f in changes if f.text_status == pysvn.wc_status_kind.conflicted] print 'unversioned files:' print [f.path for f in changes if f.text_status == pysvn.wc_status_kind.unversioned]
生成差异或补丁
import pysvn client = pysvn.Client() diff_text = client.diff('./tmp-file-prefix-', '.')
获取仓库URL
import pysvn client = pysvn.Client() entry = client.info('.') print 'Url:',entry.url
仓库任务
本节说明任务的例子, 操纵或检查仓库.虽然共同任务, 通过本地工作副本时间的变化, 这些任务直接影响到库
获取仓库目录的清单
import pysvn client = pysvn.Client() entry_list = client.ls('.')
从仓库获取文件内容
import pysvn client = pysvn.Client() file_content = client.cat('file.txt')
创建一个标记或分支
import pysvn client = pysvn.Client() log_message = "reason for change" def get_log_message(): return True, log_message client.callback_get_log_message = get_log_message client.copy('http://svnrepo.com/svn/trunk', 'http://svnrepo.com/svn/tag/%s' % tag_name )
从仓库中转移或者重命名
import pysvn client = pysvn.Client() client.move( 'file_old.txt', 'file_new.txt' )
锁定文件
import pysvn client = pysvn.Client() client.lock( 'file.txt', 'reason for locking' )
锁定文件并锁定其他用户或者工作副本
import pysvn client = pysvn.Client() client.lock( 'file.txt', 'reason for locking', force=True )
解锁
import pysvn client = pysvn.Client() client.unlock( 'file.txt' )
解锁文件并锁定其他用户或工作副本
import pysvn client = pysvn.Client() client.unlock( 'file.txt', force=True )
测试锁定文件
Method 1:
all_entries = self.client.info2( path, recurse=False ) for path, info in all_entries: if info['lock']: if info['lock']['token'] != '': print '%s is locked' % path print info['lock']['comment']
Method 2:
all_status = self.client.status( path, recurse=False, update=True ) for status in all_status: if status.entry is not None: if status.entry.lock_token is not None: print '%s is locked' % status.path
Cython代码跟Python不一样,必须要编译。
编译经过两个阶段:
* Cython编译.pyx文件为.c文件
* C编译器会把.c文件编译成.so文件(Windows上是.pyd)
以下会分节介绍几种方式来建立你的扩展模块。
注意: -a 选项,如果使用该选项将会为.c文件生成一份很漂亮的HTML文件,双击高亮的章节部分会展开代码,这对理解,优化和调试模块将会非常有帮助。
命令行
从命令行执行Cython编译器,输入选项和.pyx文件列表。
$ cython -a yourmod.pyx
会生成一个yourmod.c文件(指定-a选项会生成一个HTML文件)
编译.c文件取决于你的操作系统,请参考下如何在你的系统写Python扩展模块文档。
下面是一个Linux系统的例子:
$ gcc -shared -pthread -fPIC -fwrapv -O2 -Wall -fno-strict-aliasing \ -I/usr/include/python2.5 -o yourmod.so yourmod.c
gcc需要提供包含的文件和扩展库的链接。
在目录里会生成yourmod.so文件。
现在只需要导入你的yourmod模块就可以了。
Distutils
确保你的系统已经安装好Distutils。
下面假设需要编译的文件叫hello.pyx。
建立一个setup.py的脚本:
from distutils.core import setup from distutils.extension import Extension from Cython.Distutils import build_ext ext_modules = [Extension("hello", ["hello.pyx"])] setup( name = ’Hello world app’, cmdclass = {’build_ext’: build_ext}, ext_modules = ext_modules )
在命令行执行:python setup.py build_ext –inplace
现在可以在shell或者脚本里正常导入使用了。
Pyximport
在纯Python代码里调用Cython代码:
>>> import pyximport; pyximport.install() >>> import helloworld Hello World
这仅仅是简单调用Cython,不需要C库也不需要构建脚本。
当然也可以实验性的在Python调用。允许在Python模块中运行Cython代码在任何一个.pyx和.py模块。这包
括标准库和包。如果Cython编译失败的话,pyximport会返回到加载失败的模块处。
.py安装是这样:
>>> pyximport.install(pyimport = True)
在偶的ubuntu里编写pyrex程序编译成so还是挺爽的,用 timeit.Timer 测试性能提升不少,今天在windows也尝试了一番。
需要的工具有:
- Pyrex http://www.cosc.canterbury.ac.nz/greg.ewing/python/Pyrex/
- Dev C++ http://www.bloodshed.net/devcpp.html
Pyrex 可以通过easy_install Pyrex来安装。
Dev C++ 安装完在系统环境变量Path里加上Dev C++安装目录/bin 目录。
测试扩展代码:
# file: foo.pyx """ simple pyrex module """ cdef class Foo: """ foo doc ... """ cdef char *name def __init__(self, name): self.name = name def __repr__(self): return "foo names: %s" % (self.name) # file: setup.py from distutils.core import setup from distutils.extension import Extension from Pyrex.Distutils import build_ext setup( name='foo', ext_modules=[Extension("foo", ["foo.pyx"])], cmdclass={'build_ext':build_ext} )
写好两个文件后,进入命令提示符:
C:\>python setup.py build_ext --inplace -c mingw32 running build_ext pyrexc foo.pyx --> foo.c building 'foo' extension creating build creating build\temp.win32-2.5 creating build\temp.win32-2.5\Release C:\Program Files\DEV-CPP\Bin\gcc.exe -mno-cygwin -mdll -O -Wall -ID:\Python25\include -ID:\Python25\PC -c foo.c -o build\temp.win32-2.5\Release\foo.o writing build\temp.win32-2.5\Release\foo.def C:\Program Files\DEV-CPP\Bin\dllwrap.exe -mno-cygwin -mdll -static --entry _DllMain@12 --output-lib build\temp.win32-2.5\Release\libfoo.a --def build\temp.win32-2.5\Release\foo.def -s build\temp.win32-2.5\Release\foo.o -LD:\Python25\libs -L D:\Python25\PCBuild -lpython25 -lmsvcr71 -o foo.pyd
编译完毕,可以看到当前目录下多了:build目录、foo.c、foo.pyd。foo.pyd 即是编译好的二进制扩展。
C:\>python ActivePython 2.5.0.0 (ActiveState Software Inc.) based on Python 2.5 (r25:51908, Mar 9 2007, 17:40:28) [MSC v.1310 32 bit (Intel)] on win32 Type "help", "copyright", "credits" or "license" for more information. >>> import foo <module 'foo' from 'foo.pyd'> >>> foo.Foo("smallfish") foo names: smallfish
其实MySQL-python安装很简直,以前一直也没在意,今天发觉换了1.2.3新版本,ms蹦出很多问题来了。
做个记录,防止以后有问题无处可查。
一般步骤是:
1. 安装easy_install
shell > wget http://peak.telecommunity.com/dist/ez_setup.py shell > python ez_setup.py
会自动根据本机的py版本选择对应的egg,安装完可以看到有/usr/bin/easy_install程序了
2. 安装MySQL-python
shell > easy_install MySQL-python
到这里安装算是完成了,不过接下来测试就郁闷了。
在import MySQLdb出现了两个错误:
a). ImportError: libmysqlclient_r.so.15: cannot open shared object file: No such file or directory
这个错误一般解决比较简单,把路径加入到LD_LIBRARY_PATH即可,不过偶的现象比较强,因为没装MySQL,哈哈
b). ImportError: /lib/tls/libc.so.6: version `GLIBC_2.4' not found
解决这个错误的办法是不用easy_install了,直接下载MySQL-python-1.2.2.tar.gz包,然后就是三步走:
shell > tar zxvf MySQL-python-1.2.2.tar.gz shell > cd MySQL-python-1.2.2 shell > python setup.py install
看到有人在坛子里询问在GAE如何发布web.py有关问题,就尝试了一把。具体安装和使用过程如下,请对照自己本地路径相应修改:
1. 复制本地对应web.py目录到GAE对应应用目录
比如:D:\Python25\Lib\site-packages\web 到 e:\googleapp\pynotes
2. 写测试代码
app.yaml
application: pynotes version: 1 runtime: python api_version: 1 handlers: - url: /.* script: home.py
home.py
import web render = web.template.render('templates/') urls = ( '/', 'index' ) class index: def GET(self): web.header('Content-type', 'text/html') name = 'smallfish' return render.index(name) app = web.application(urls, globals()) main = app.cgirun() # 这行是发布到GAE的关键
templates/index.html
$def with (name) hello, $name. test by web.py
3. 发布到GAE,测试
e:\googleapp>appcfg.py update pynotes/
到这里,一个简单web.py应用就完成了,然后刷新。GAE显示500 Error!
看后台GAE Log显示错误信息:”No module named templates“,去web.py官方溜达了一圈,发现在其cookbook里有一篇文档《How to use templates on Google App Engine》,里面说的很明白啦。
因为web.py的模板在GAE上文件系统会有所限制,所有本地得compile一下,具体命令是:python web/template.py –compile templates 最后一个参数是本地对应模板目录templates,如果有多个模板目录则一次运行一次。运行完会在templates会生成一个__init__.py,里面内容有兴趣可以看看,很眼熟的哦。
4. 再次发布到GAE,可以看到OK拉!
Mako是什么?Moko是Python写的一个模板库,Python官网python.org用的就是它哦。其他废话也就不累赘了,直接来点代码,方便阅读与了解把。
(Mako官网地址:http://www.makotemplates.org/ ,可以下载安装包,推荐使用easy_install安装)
from mako.template import Template mytemplate = Template("hello world!") print mytemplate.render() mytemplate = Template("hello, ${name}!") print mytemplate.render(name="jack")
代码可以参考官方doc部分
mytemplate = Template(filename='/docs/mytmpl.txt') print mytemplate.render()
还可以从设置模板为文件,设置filename属性
mytemplate = Template(filename='/docs/mytmpl.txt', module_directory='/tmp/mako_modules') print mytemplate.render()
文件还可以缓存到某个目录下,下面的/docs/mytmpl.txt会产生一个py:/tmp/mako_modules/docs/mytmpl.txt.py
from mako.lookup import TemplateLookup mylookup = TemplateLookup(directories=['/docs']) mytemplate = Template("""<%include file="header.txt"/> hello world!""", lookup=mylookup)
查找模板,方便统一模板路径使用。
mylookup = TemplateLookup(directories=['/docs'], module_directory='/tmp/mako_modules') def serve_template(templatename, **kwargs): mytemplate = mylookup.get_template(templatename) print mytemplate.render(**kwargs)
改良了上面的查找方式
mylookup = TemplateLookup(directories=['/docs'], output_encoding='utf-8', encoding_errors='replace') mytemplate = mylookup.get_template("foo.txt") print mytemplate.render()
设置输出编码,以及编码错误时候处理方式
MySQLdb默认查询结果都是返回tuple,输出时候不是很方便,必须按照0,1这样读取,无意中在网上找到简单的修改方法,就是传递一个cursors.DictCursor就行。
默认程序:
import MySQLdb db = MySQLdb.connect(host = 'localhost', user = 'root', passwd = '123456', db = 'test') cursor = db.cursor() cursor.execute('select * from user') rs = cursor.fetchall() print rs # 返回类似如下 # ((1000L, 0L), (2000L, 0L), (3000L, 0L))
修改后:
import MySQLdb import MySQLdb.cursors db = MySQLdb.connect(host = 'localhost', user = 'root', passwd = '123456', db = 'test', cursorclass = MySQLdb.cursors.DictCursor) cursor = db.cursor() cursor.execute('select * from user') rs = cursor.fetchall() print rs # 返回类似如下 # ({'age': 0L, 'num': 1000L}, {'age': 0L, 'num': 2000L}, {'age': 0L, 'num': 3000L})
或者也可以用下面替换connect和cursor部分
db = MySQLdb.connect(host = 'localhost', user = 'root', passwd = '123456', db = 'test') cursor = conn.cursor(cursorclass = MySQLdb.cursors.DictCursor)