Mar
3
早先翻python代码的时候是有注意到 intobject 里有缓存这档事,不过没细看。昨天有人在sf问起为什么有如下现象:
于是又翻开python的源码 python2.6.8/Objects/intobject.c ,可以看到这些代码(略做简化):
而PyFloat_Object并没有(也不适合)实现这样的缓存,所以就可以解释上面的情况了。
更进一步,可以用257来验证一下,的确是超出了缓存的范围:
然后手贱做了另一个测试,蛋疼了:
也就是说如果让解释器一次执行的话,解释器又会再优化它,让a、b引用同一个对象。//注:这里对于float和str类型的常量效果是一样的。
为了搞清楚解释器到底是怎么实现这一点的,又把代码翻出来。之前翻的时候对解释器的执行流程已经有大致的了解了。make得到的python解释器是从 Module/python.c 里的 main() 函数开始的,调用链大约是这样:
从 PyRun_FileExFlags 开始,才能看到底层代码正式登场:
注:更详细的Python编译解释流程可参见这一系列: http://blog.csdn.net/atfield/article/category/256448
通过加入一些调试代码,可以窥探到内部的执行流。例如,在PyParser_AddToken中输出token的名称和类型编码;在PyParser_ParseFileFlagsEx()之后调用PyNode_ListTree(),可以看到生成的CST树(修改list1node()可以让它打印出更容易阅读的版本);修改PyInt_FromLong(),让它在ival=257的时候输出创建的object的id(CPython实现中 id 其实就是指针的值)。加上一些代码以后,编译python,执行test.py可以看到如下输出:
从输出可以看到,解析源码生成CST的时候(输出的CST已经滤掉了非TERMINAL的node),每个token还保留着原始的字符串(例如0x101和257),而在CST到AST的转换过程中(PyAST_FromNode),解释器为每一个NUMBER都创建了一个PyIntObject。然而在程序运行的最终结果里可以看到,a is b的结果是True,也就是说,从AST转换到CFG并执行(run_mod)的过程中,解释器做了适量的优化,将 a 和 b 都指向了同一个 int 对象。
由于对CFG不熟,相应的代码还不太看得懂,所以暂时只能烂尾了,如果以后看懂了,再来补充。
续集:Python int缓存的那点事[续]
引用
>>> a = 1.0
>>> b = 1.0
>>> a is b
False
>>> a = 1
>>> b = 1
>>> a is b
True
>>> b = 1.0
>>> a is b
False
>>> a = 1
>>> b = 1
>>> a is b
True
于是又翻开python的源码 python2.6.8/Objects/intobject.c ,可以看到这些代码(略做简化):
#define NSMALLPOSINTS 257
#define NSMALLNEGINTS 5
/* References to small integers are saved in this array so that they
can be shared.
The integers that are saved are those in the range
-NSMALLNEGINTS (inclusive) to NSMALLPOSINTS (not inclusive).
*/
static PyIntObject *small_ints[NSMALLNEGINTS + NSMALLPOSINTS];
PyObject *
PyInt_FromLong(long ival)
{
register PyIntObject *v;
//如果 -5 <= ival && ival < 257, 命中缓存~
if (-NSMALLNEGINTS <= ival && ival < NSMALLPOSINTS) {
v = small_ints[ival + NSMALLNEGINTS];
Py_INCREF(v);
return (PyObject *) v;
}
if (free_list == NULL) { //这个是另一个优化,相当于内存池,用链表实现
if ((free_list = fill_free_list()) == NULL)
return NULL;
}
/* Inline PyObject_New */
v = free_list;
free_list = (PyIntObject *)Py_TYPE(v);
PyObject_INIT(v, &PyInt_Type);
v->ob_ival = ival;
return (PyObject *) v;
}
#define NSMALLNEGINTS 5
/* References to small integers are saved in this array so that they
can be shared.
The integers that are saved are those in the range
-NSMALLNEGINTS (inclusive) to NSMALLPOSINTS (not inclusive).
*/
static PyIntObject *small_ints[NSMALLNEGINTS + NSMALLPOSINTS];
PyObject *
PyInt_FromLong(long ival)
{
register PyIntObject *v;
//如果 -5 <= ival && ival < 257, 命中缓存~
if (-NSMALLNEGINTS <= ival && ival < NSMALLPOSINTS) {
v = small_ints[ival + NSMALLNEGINTS];
Py_INCREF(v);
return (PyObject *) v;
}
if (free_list == NULL) { //这个是另一个优化,相当于内存池,用链表实现
if ((free_list = fill_free_list()) == NULL)
return NULL;
}
/* Inline PyObject_New */
v = free_list;
free_list = (PyIntObject *)Py_TYPE(v);
PyObject_INIT(v, &PyInt_Type);
v->ob_ival = ival;
return (PyObject *) v;
}
而PyFloat_Object并没有(也不适合)实现这样的缓存,所以就可以解释上面的情况了。
更进一步,可以用257来验证一下,的确是超出了缓存的范围:
引用
>>> a = 257
>>> b = 257
>>> a is b
False
>>> b = 257
>>> a is b
False
然后手贱做了另一个测试,蛋疼了:
引用
>>> a = 257; b = 257; a is b
True
True
也就是说如果让解释器一次执行的话,解释器又会再优化它,让a、b引用同一个对象。//注:这里对于float和str类型的常量效果是一样的。
为了搞清楚解释器到底是怎么实现这一点的,又把代码翻出来。之前翻的时候对解释器的执行流程已经有大致的了解了。make得到的python解释器是从 Module/python.c 里的 main() 函数开始的,调用链大约是这样:
引用
main() @Modules/python.c
Py_Main() @Modules/main.c
PyRun_AnyFileExFlags() @Python/pythonrun.c
PyRun_SimpleFileExFlags
PyRun_FileExFlags()
Py_Main() @Modules/main.c
PyRun_AnyFileExFlags() @Python/pythonrun.c
PyRun_SimpleFileExFlags
PyRun_FileExFlags()
从 PyRun_FileExFlags 开始,才能看到底层代码正式登场:
引用
PyRun_FileExFlags()
mod_ty *mod = PyParser_ASTFromFile() //把文件转换成AST(Abstract Syntax Tree)
node *n = PyParser_ParseFileFlagsEx() //生成CST(Concrete Syntax Tree)
parsetoke() //逐个解析token
ps = PyParser_New()
for (;;)
PyTokenizer_Get() //获取下一个token
PyParser_AddToken(ps, ...) //将token加入到CST中
mod = PyAST_FromNode(n, ...) //将CST转换成AST
递归调用 ast_for_xxx 生成AST,同时过滤CST中的冗余信息
其中ast_for_atom中调用了parsenumber, 它调用PyInt_FromLong()
run_mod(mod, ...) //执行AST
co = PyAST_Compile(mod, ...) //将AST转换成CFG(Control Flow Graph) bytecode
PyFuture_FromAST()
PySymtable_Build() //创建符号表
co = compiler_mod() //编译ast为bytecode
PyEval_EvalCode(co, ...) //执行bytecode
PyEval_EvalCodeEx()
mod_ty *mod = PyParser_ASTFromFile() //把文件转换成AST(Abstract Syntax Tree)
node *n = PyParser_ParseFileFlagsEx() //生成CST(Concrete Syntax Tree)
parsetoke() //逐个解析token
ps = PyParser_New()
for (;;)
PyTokenizer_Get() //获取下一个token
PyParser_AddToken(ps, ...) //将token加入到CST中
mod = PyAST_FromNode(n, ...) //将CST转换成AST
递归调用 ast_for_xxx 生成AST,同时过滤CST中的冗余信息
其中ast_for_atom中调用了parsenumber, 它调用PyInt_FromLong()
run_mod(mod, ...) //执行AST
co = PyAST_Compile(mod, ...) //将AST转换成CFG(Control Flow Graph) bytecode
PyFuture_FromAST()
PySymtable_Build() //创建符号表
co = compiler_mod() //编译ast为bytecode
PyEval_EvalCode(co, ...) //执行bytecode
PyEval_EvalCodeEx()
注:更详细的Python编译解释流程可参见这一系列: http://blog.csdn.net/atfield/article/category/256448
通过加入一些调试代码,可以窥探到内部的执行流。例如,在PyParser_AddToken中输出token的名称和类型编码;在PyParser_ParseFileFlagsEx()之后调用PyNode_ListTree(),可以看到生成的CST树(修改list1node()可以让它打印出更容易阅读的版本);修改PyInt_FromLong(),让它在ival=257的时候输出创建的object的id(CPython实现中 id 其实就是指针的值)。加上一些代码以后,编译python,执行test.py可以看到如下输出:
引用
felix021@ubuntu-server:~/src/python2.7-2.7.3$ cat test.py
a = 257
b = 0x101
print a is b
felix021@ubuntu-server:~/src/python2.7-2.7.3$ ./python -d test.py
PyParser_ParseFileFlagsEx
type = 1, token: [a]
type = 22, token: [=]
type = 2, token: [257]
type = 4, token: []
type = 1, token: [b]
type = 22, token: [=]
type = 2, token: [0x101]
type = 4, token: []
type = 1, token: [print]
type = 1, token: [a]
type = 1, token: [is]
type = 1, token: [b]
type = 4, token: []
type = 4, token: []
type = 0, token: []
PyNode_ListTree:
<1>a //type=1表示是NAME
<22>=
<2>257 //type=2表示是NUMBER
<4> //这是NEWLINE
<1>b
<22>=
<2>0x101
<4>
<1>print
<1>a
<1>is
<1>b
<4>
<4>
<0> //这是ENDMARKER
Before PyAST_FromNode
name = a
ival = 257, id = 22699048 //注意这个id和下一个id不一样
name = b
ival = 257, id = 22698784
name = b
name = a
After PyAST_FromNode
True #这一行是print a is b的输出
a = 257
b = 0x101
print a is b
felix021@ubuntu-server:~/src/python2.7-2.7.3$ ./python -d test.py
PyParser_ParseFileFlagsEx
type = 1, token: [a]
type = 22, token: [=]
type = 2, token: [257]
type = 4, token: []
type = 1, token: [b]
type = 22, token: [=]
type = 2, token: [0x101]
type = 4, token: []
type = 1, token: [print]
type = 1, token: [a]
type = 1, token: [is]
type = 1, token: [b]
type = 4, token: []
type = 4, token: []
type = 0, token: []
PyNode_ListTree:
<1>a //type=1表示是NAME
<22>=
<2>257 //type=2表示是NUMBER
<4> //这是NEWLINE
<1>b
<22>=
<2>0x101
<4>
<1>print
<1>a
<1>is
<1>b
<4>
<4>
<0> //这是ENDMARKER
Before PyAST_FromNode
name = a
ival = 257, id = 22699048 //注意这个id和下一个id不一样
name = b
ival = 257, id = 22698784
name = b
name = a
After PyAST_FromNode
True #这一行是print a is b的输出
从输出可以看到,解析源码生成CST的时候(输出的CST已经滤掉了非TERMINAL的node),每个token还保留着原始的字符串(例如0x101和257),而在CST到AST的转换过程中(PyAST_FromNode),解释器为每一个NUMBER都创建了一个PyIntObject。然而在程序运行的最终结果里可以看到,a is b的结果是True,也就是说,从AST转换到CFG并执行(run_mod)的过程中,解释器做了适量的优化,将 a 和 b 都指向了同一个 int 对象。
由于对CFG不熟,相应的代码还不太看得懂,所以暂时只能烂尾了,如果以后看懂了,再来补充。
续集:Python int缓存的那点事[续]
Jan
20
这周写了个模块,让python可以跟后台的C服务通信了。
总体上来说给python写个模块还是比较容易的,比给php写模块要舒服多了,但是还是遇到一个问题:给php写模块的时候可以用MINIT完成初始化、MSHUTDOWN完成清理;给Python写模块,有对应的模块初始化函数,但是却没有对应的清理函数,实在是令人蛋疼。
需求其实很简单(应该也很普遍吧?),只是要在python退出之前执行一点代码,保证在初始化的时候分配的一些资源能够被释放;但是模块本身并没有提供这样的机制,只好想其他办法了。
最简单的是用C语言本身提供的 atexit ,不过这是在main()结束或者调用exit()时 - 也就是说,在整个C环境要结束了的情况下才会运行(但是在关闭所有打开的文件之前)。
虽然有效,但是由于它不是python提供的机制,多少让人有点不太放心,所以还是看看别人怎么用的吧。
Google搜了一下"python module destructor",stackoverflow上说的是,可以用python的 atexit 模块。这是一个纯python模块,源码可以在 /usr/lib/pythonX.X/atexit.py 看到,其实就是通过 atexit.register() 注册退出函数,并将 atexit._run_exitfuncs() 函数绑定到 sys.exitfunc 。sys.exitfunc会在Py_Finalize()这个函数中被调用(call_sys_exitfunc())。
在C模块里调用它,最简单的办法是在模块的初始化函数里加入类似这样一句(注意换行和缩进):
这大致相当于在python脚本里用eval执行了一段代码(没有指定globals、locals和compile_flag)。也可以用如下几乎等价的纯C代码
由于错误处理、引用计数的代码占了一大半,所以代码这么长……
这种方法的好处是,它在python运行环境结束之前执行注册的函数,所以注册的函数仍然可以使用绝大部分python提供的功能(你甚至可以再import新的模块,但是最好别用thread...)。然而atexit有一个蛋疼的缺陷:尽管python在sys模块的doc里写的是“Assigning to sys.exitfunc is deprecated; use the atexit module instead.”,但是sys.exitfunc仍然是任意python脚本都可以修改的!所以通过atexit.register()注册的函数并不能100%保证被运行。
再回过头来看Py_Finalize()函数,这里其实是有很大槽点的:它有一个长达十一行的"sundry finalizers"段!
也就是说,它为许多内置的module逐一硬编码了清理函数,却没有实现一个清理机制!
甚至在这后面还有一段注释:
再往下看,在Py_Finalize()的最后一行终于出现了本篇的另一个主角:call_ll_exitfuncs(); 这个函数的内容很简单:逐一执行 exitfuncs 这个数组里保存的函数。而 exitfuncs 这个数组的内容,则是由 Py_AtExit() 函数填进去的:
我最终采用的解决方案是这个:Py_AtExit()。
最后总结对比一下上述3种方案:
1. C语言的 atexit() : 使用链表保存注册的函数,只要内存够,数量没限制。在Python完全结束后执行。
2. Python的 atexit 模块:使用 list 保存注册的函数,在register的时候还可以带参数,在Python解释器仍然完整的情况下执行,一般来说很够用;但由于使用的sys.exitfunc可能会被其他脚本使用,并不能100%保证有效,故,有强迫症的慎用。或者应当在doc里明确说明。
3. Python C API的 Py_AtExit() :在Python环境几乎完全结束的时候被调用,最多只能注册32个函数(所以最好要检查返回值)。其实从流程上来说,在这之后马上就是main的return或者exit()函数调用,所以跟atexit基本上差不多。
p.s. 另一个尝试但是失败了的方案:建立一个新的type X_Type,在它的tp_dealloc里头写入清理代码,以X_Type建立一个新的class X,在模块的初始化函数中new一个X,撂着,并期望Py_Finalize()里的PyGC_Collect()会把它回收掉。不知道还差了点什么,如果哪位大牛知道,还望不吝赐教。
总体上来说给python写个模块还是比较容易的,比给php写模块要舒服多了,但是还是遇到一个问题:给php写模块的时候可以用MINIT完成初始化、MSHUTDOWN完成清理;给Python写模块,有对应的模块初始化函数,但是却没有对应的清理函数,实在是令人蛋疼。
需求其实很简单(应该也很普遍吧?),只是要在python退出之前执行一点代码,保证在初始化的时候分配的一些资源能够被释放;但是模块本身并没有提供这样的机制,只好想其他办法了。
最简单的是用C语言本身提供的 atexit ,不过这是在main()结束或者调用exit()时 - 也就是说,在整个C环境要结束了的情况下才会运行(但是在关闭所有打开的文件之前)。
虽然有效,但是由于它不是python提供的机制,多少让人有点不太放心,所以还是看看别人怎么用的吧。
Google搜了一下"python module destructor",stackoverflow上说的是,可以用python的 atexit 模块。这是一个纯python模块,源码可以在 /usr/lib/pythonX.X/atexit.py 看到,其实就是通过 atexit.register() 注册退出函数,并将 atexit._run_exitfuncs() 函数绑定到 sys.exitfunc 。sys.exitfunc会在Py_Finalize()这个函数中被调用(call_sys_exitfunc())。
在C模块里调用它,最简单的办法是在模块的初始化函数里加入类似这样一句(注意换行和缩进):
PyRun_SimpleString(
"import atexit\n"
"def __modname_clean():\n"
" import modname\n"
" modname.clean()\n"
"atexit.register(__modname_clean)\n");
"import atexit\n"
"def __modname_clean():\n"
" import modname\n"
" modname.clean()\n"
"atexit.register(__modname_clean)\n");
这大致相当于在python脚本里用eval执行了一段代码(没有指定globals、locals和compile_flag)。也可以用如下几乎等价的纯C代码
//注:m是在模块初始化函数中的定义的当前模块对象
PyObject *pClean = PyObject_GetAttrString(m, "clean");
if (pClean != NULL)
{
PyObject *pName = PyString_FromString("atexit");
PyObject *pMod = PyImport_Import(pName);
Py_DECREF(pName);
if (pMod != NULL)
{
PyObject *pFunc = PyObject_GetAttrString(pMod, "register");
if (pFunc && PyCallable_Check(pFunc))
{
PyObject *pArgs = Py_BuildValue("(O)", pClean);
PyObject *ret = PyObject_CallObject(pFunc, pArgs);
//if (ret == NULL) ; //sth went wrong
Py_XDECREF(pArgs);
Py_XDECREF(ret);
}
Py_XDECREF(pFunc);
}
Py_XDECREF(pMod);
}
Py_XDECREF(pClean);
PyObject *pClean = PyObject_GetAttrString(m, "clean");
if (pClean != NULL)
{
PyObject *pName = PyString_FromString("atexit");
PyObject *pMod = PyImport_Import(pName);
Py_DECREF(pName);
if (pMod != NULL)
{
PyObject *pFunc = PyObject_GetAttrString(pMod, "register");
if (pFunc && PyCallable_Check(pFunc))
{
PyObject *pArgs = Py_BuildValue("(O)", pClean);
PyObject *ret = PyObject_CallObject(pFunc, pArgs);
//if (ret == NULL) ; //sth went wrong
Py_XDECREF(pArgs);
Py_XDECREF(ret);
}
Py_XDECREF(pFunc);
}
Py_XDECREF(pMod);
}
Py_XDECREF(pClean);
由于错误处理、引用计数的代码占了一大半,所以代码这么长……
这种方法的好处是,它在python运行环境结束之前执行注册的函数,所以注册的函数仍然可以使用绝大部分python提供的功能(你甚至可以再import新的模块,但是最好别用thread...)。然而atexit有一个蛋疼的缺陷:尽管python在sys模块的doc里写的是“Assigning to sys.exitfunc is deprecated; use the atexit module instead.”,但是sys.exitfunc仍然是任意python脚本都可以修改的!所以通过atexit.register()注册的函数并不能100%保证被运行。
再回过头来看Py_Finalize()函数,这里其实是有很大槽点的:它有一个长达十一行的"sundry finalizers"段!
/* Sundry finalizers */
PyMethod_Fini();
PyFrame_Fini();
PyCFunction_Fini();
PyTuple_Fini();
PyList_Fini();
PySet_Fini();
PyString_Fini();
PyByteArray_Fini();
PyInt_Fini();
PyFloat_Fini();
PyDict_Fini();
PyMethod_Fini();
PyFrame_Fini();
PyCFunction_Fini();
PyTuple_Fini();
PyList_Fini();
PySet_Fini();
PyString_Fini();
PyByteArray_Fini();
PyInt_Fini();
PyFloat_Fini();
PyDict_Fini();
也就是说,它为许多内置的module逐一硬编码了清理函数,却没有实现一个清理机制!
甚至在这后面还有一段注释:
/* XXX Still allocated:
- various static ad-hoc pointers to interned strings
- int and float free list blocks
- whatever various modules and libraries allocate
*/
坑爹啊。。。- various static ad-hoc pointers to interned strings
- int and float free list blocks
- whatever various modules and libraries allocate
*/
再往下看,在Py_Finalize()的最后一行终于出现了本篇的另一个主角:call_ll_exitfuncs(); 这个函数的内容很简单:逐一执行 exitfuncs 这个数组里保存的函数。而 exitfuncs 这个数组的内容,则是由 Py_AtExit() 函数填进去的:
#define NEXITFUNCS 32 //另一个坑:最多只能注册32个exitfunc
static void (*exitfuncs[NEXITFUNCS])(void);
static int nexitfuncs = 0;
int Py_AtExit(void (*func)(void))
{
if (nexitfuncs >= NEXITFUNCS)
return -1;
exitfuncs[nexitfuncs++] = func;
return 0;
}
static void (*exitfuncs[NEXITFUNCS])(void);
static int nexitfuncs = 0;
int Py_AtExit(void (*func)(void))
{
if (nexitfuncs >= NEXITFUNCS)
return -1;
exitfuncs[nexitfuncs++] = func;
return 0;
}
我最终采用的解决方案是这个:Py_AtExit()。
最后总结对比一下上述3种方案:
1. C语言的 atexit() : 使用链表保存注册的函数,只要内存够,数量没限制。在Python完全结束后执行。
2. Python的 atexit 模块:使用 list 保存注册的函数,在register的时候还可以带参数,在Python解释器仍然完整的情况下执行,一般来说很够用;但由于使用的sys.exitfunc可能会被其他脚本使用,并不能100%保证有效,故,有强迫症的慎用。或者应当在doc里明确说明。
3. Python C API的 Py_AtExit() :在Python环境几乎完全结束的时候被调用,最多只能注册32个函数(所以最好要检查返回值)。其实从流程上来说,在这之后马上就是main的return或者exit()函数调用,所以跟atexit基本上差不多。
p.s. 另一个尝试但是失败了的方案:建立一个新的type X_Type,在它的tp_dealloc里头写入清理代码,以X_Type建立一个新的class X,在模块的初始化函数中new一个X,撂着,并期望Py_Finalize()里的PyGC_Collect()会把它回收掉。不知道还差了点什么,如果哪位大牛知道,还望不吝赐教。
Dec
13
这事儿其实只要有了拼音库,就挺简单的。我从 pinyin4j 这个项目里搞了一份出来。虽然这个库是为java写的,不过要提取倒是相当简单:到这里 下载pinyin4j-2.5.0.zip,解压得到里头的 lib/pinyin4j-2.5.0.jar ,再解压得到里头的 pinyindb/unicode_to_hanyu_pinyin.txt 。
这个文件的结构很简单,每一行的基本结构是这样的:
前面的 4E04 是汉字的Unicode编码,空格分隔,然后括号里面包含了所有读音(而且看起来像是按频率排序好了的),每个读音分别给出了拼音的声母韵母以及声调。
在这个基础上解析它就相当简单了。
当然,你也可以把它存入一个key/value数据库(memcachedb什么的就挺好)。以上面的例子的话,把一个字符串中的汉字转换成拼音就很简单了:
要注意的一点是,“驴”的拼音被标记为 lu:2 ,而不是打字时习惯使用的 lv ,如果有需要的,还得再加个简单的转换逻辑。
实际上这个库里头还支持通用拼音(貌似是台湾地区使用过的)等其他转换方式,有需要的同学可以自己考据一下其结构。
p.s. 在python里头可以用 unichr(0x4E04) 得到这个unicode对应的字符,也可以用 ord('率'.decode('utf-8')) 得到这个字符的unicode编码(注意替换字符的原始编码)。至于十进制和十六进制的转换,也很简单, hex(32768) 得到 '0x8000' 而int('0x8000', 16)就能得到32768。python真好用。
这个文件的结构很简单,每一行的基本结构是这样的:
引用
4E04 (shang4,shang3)
前面的 4E04 是汉字的Unicode编码,空格分隔,然后括号里面包含了所有读音(而且看起来像是按频率排序好了的),每个读音分别给出了拼音的声母韵母以及声调。
在这个基础上解析它就相当简单了。
f = open("unicode_to_hanyu_pinyin.txt", "r")
py = {}
for l in f:
l = l.strip() #行末回车
key, val = l.split(' ')
sd = val[1:-1].replace('u:', 'v').split(',') #去掉左右括号,把u:转为v(驴 lv),然后按逗号分隔
arr_sd = []
for i in sd:
arr_sd.append({'py': i[0:-1], 'tone': i[-1]}) #把每个读音的声调分离出来
py[int(key, 16)] = arr_sd #把unicode编码转成10进制作为key
f.close()
py = {}
for l in f:
l = l.strip() #行末回车
key, val = l.split(' ')
sd = val[1:-1].replace('u:', 'v').split(',') #去掉左右括号,把u:转为v(驴 lv),然后按逗号分隔
arr_sd = []
for i in sd:
arr_sd.append({'py': i[0:-1], 'tone': i[-1]}) #把每个读音的声调分离出来
py[int(key, 16)] = arr_sd #把unicode编码转成10进制作为key
f.close()
当然,你也可以把它存入一个key/value数据库(memcachedb什么的就挺好)。以上面的例子的话,把一个字符串中的汉字转换成拼音就很简单了:
def convert(str, encoding = 'utf-8'):
ret = ''
for i in str.decode(encoding):
w = ord(i)
if py.has_key(w):
ret += "%s-%s " % (py[w][0]['py'] , py[w][0]['tone'])
else:
ret += i
return ret
ret = ''
for i in str.decode(encoding):
w = ord(i)
if py.has_key(w):
ret += "%s-%s " % (py[w][0]['py'] , py[w][0]['tone'])
else:
ret += i
return ret
要注意的一点是,“驴”的拼音被标记为 lu:2 ,而不是打字时习惯使用的 lv ,如果有需要的,还得再加个简单的转换逻辑。
实际上这个库里头还支持通用拼音(貌似是台湾地区使用过的)等其他转换方式,有需要的同学可以自己考据一下其结构。
p.s. 在python里头可以用 unichr(0x4E04) 得到这个unicode对应的字符,也可以用 ord('率'.decode('utf-8')) 得到这个字符的unicode编码(注意替换字符的原始编码)。至于十进制和十六进制的转换,也很简单, hex(32768) 得到 '0x8000' 而int('0x8000', 16)就能得到32768。python真好用。
Oct
31
在shell里头,有些程序(比如ssh)的交互是从pty中读取的数据,直接的重定向无法解决,因此需要用到expect这种东西。不过这货的编译比较麻烦,各种依赖。幸好有个pexpect是纯python的,存档留个记录。
如果密码错误的话,输出
如果正确,则输出
#!/usr/bin/python
import sys
import pexpect
password = 'password'
expect_list = ['(yes/no)', 'password:']
p = pexpect.spawn('ssh username@localhost ls')
try:
while True:
idx = p.expect(expect_list)
print p.before + expect_list[idx],
if idx == 0:
print "yes"
p.sendline('yes')
elif idx == 1:
print password
p.sendline(password)
except pexpect.TIMEOUT:
print >>sys.stderr, 'timeout'
except pexpect.EOF:
print p.before
import sys
import pexpect
password = 'password'
expect_list = ['(yes/no)', 'password:']
p = pexpect.spawn('ssh username@localhost ls')
try:
while True:
idx = p.expect(expect_list)
print p.before + expect_list[idx],
if idx == 0:
print "yes"
p.sendline('yes')
elif idx == 1:
print password
p.sendline(password)
except pexpect.TIMEOUT:
print >>sys.stderr, 'timeout'
except pexpect.EOF:
print p.before
如果密码错误的话,输出
引用
The authenticity of host 'localhost (127.0.0.1)' can't be established.
RSA key fingerprint is fe:00:00::00:00:00:00:00:00:00:00:00:00:00:00.
Are you sure you want to continue connecting ((yes/no) yes
)? yes
Warning: Permanently added 'localhost' (RSA) to the list of known hosts.
username@localhost's password: password
Permission denied, please try again.
username@localhost's password: password
Permission denied, please try again.
username@localhost's password: password
Permission denied (publickey,gssapi-with-mic,password).
RSA key fingerprint is fe:00:00::00:00:00:00:00:00:00:00:00:00:00:00.
Are you sure you want to continue connecting ((yes/no) yes
)? yes
Warning: Permanently added 'localhost' (RSA) to the list of known hosts.
username@localhost's password: password
Permission denied, please try again.
username@localhost's password: password
Permission denied, please try again.
username@localhost's password: password
Permission denied (publickey,gssapi-with-mic,password).
如果正确,则输出
引用
username@localhost's password: password
a
b
bin
code
a
b
bin
code
Oct
31
一个蛋疼的服务:向某个端口提供shell服务。
基本流程是这样的:python虚拟一个终端,载入了一个bash;然后呢,监听某个端口,连上该端口的客户端的输入当作bash的输入,将bash的输出返回给该客户端。当客户端断开的时候,bash继续运行,等待下一个客户端。
之所以倒腾出这么个东西,主要是突然想写个php在一个session里面完成一系列任务(甚至su成另外一个用户),但是system之类的函数就很难搞。基本上就是没用的东西。
基本流程是这样的:python虚拟一个终端,载入了一个bash;然后呢,监听某个端口,连上该端口的客户端的输入当作bash的输入,将bash的输出返回给该客户端。当客户端断开的时候,bash继续运行,等待下一个客户端。
之所以倒腾出这么个东西,主要是突然想写个php在一个session里面完成一系列任务(甚至su成另外一个用户),但是system之类的函数就很难搞。基本上就是没用的东西。
#!/usr/bin/python
import socket
import os
import thread
import pty
tty = open("/dev/tty", "w")
shell_input_reader, shell_input_writer = os.pipe()
shell_output_reader, shell_output_writer = os.pipe()
def sheller(arg):
global shell, shell_input_reader, shell_output_writer
os.dup2(shell_input_reader, 0)
os.dup2(shell_output_writer, 1)
os.dup2(shell_output_writer, 2)
while True:
pty.spawn('/bin/bash')
def shell_to_sock(conn):
global shell_output_reader
while True:
try:
conn.send(os.read(shell_output_reader, 1024))
except:
break
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sock.bind(('127.0.0.1', 4698)) #如果希望这个服务对其他机器开放的话,那就把这个ip改成0.0.0.0或者外网的ip
sock.listen(5)
thread.start_new_thread(sheller, (1,))
while True:
conn, addr = sock.accept()
print >>tty, "%s:%d connected" % (addr[0], addr[1])
thread.start_new_thread(shell_to_sock, (conn, ))
while True:
try:
buf = conn.recv(1024)
if not buf:
break
print >>tty, "[%s]" % buf.strip()
os.write(shell_input_writer, buf)
except:
break
conn.close()
import socket
import os
import thread
import pty
tty = open("/dev/tty", "w")
shell_input_reader, shell_input_writer = os.pipe()
shell_output_reader, shell_output_writer = os.pipe()
def sheller(arg):
global shell, shell_input_reader, shell_output_writer
os.dup2(shell_input_reader, 0)
os.dup2(shell_output_writer, 1)
os.dup2(shell_output_writer, 2)
while True:
pty.spawn('/bin/bash')
def shell_to_sock(conn):
global shell_output_reader
while True:
try:
conn.send(os.read(shell_output_reader, 1024))
except:
break
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sock.bind(('127.0.0.1', 4698)) #如果希望这个服务对其他机器开放的话,那就把这个ip改成0.0.0.0或者外网的ip
sock.listen(5)
thread.start_new_thread(sheller, (1,))
while True:
conn, addr = sock.accept()
print >>tty, "%s:%d connected" % (addr[0], addr[1])
thread.start_new_thread(shell_to_sock, (conn, ))
while True:
try:
buf = conn.recv(1024)
if not buf:
break
print >>tty, "[%s]" % buf.strip()
os.write(shell_input_writer, buf)
except:
break
conn.close()
Oct
28
ppt中保存的默认是原始照片;可以把照片另存,但是保存的图片实际上是压缩过后的图片,而且EXIF信息也丢失了;另存为pptx再改成zip,发现效果相同。为了找回最原始的照片,可以将其另存为xml格式,然后再写程序将其中的图片信息提取出来。
xml格式中图片的信息大致是这样存储的:
写了个简单的python脚本来完成:
xml格式中图片的信息大致是这样存储的:
引用
<pkg:part pkg:name="/ppt/media/image5.jpeg" pkg:contentType="image/jpeg" pkg:compression="store"><pkg:binaryData> [[base64 encoded data]] </pkg:binaryData></pkg:part>
写了个简单的python脚本来完成:
import re
import base64
import os
x = open("1.xml", "r")
str = x.read()
r = re.compile(r'<pkg:part pkg:name="[^"]*\/([^"]*)".*?>\s*<pkg:binaryData>((.|\n)*?)<\/pkg:binaryData>', re.S)
m = re.findall(r, str)
os.system("mkdir files")
for i in m:
print "write %s" % i[0]
f = open("./files/" + i[0], "wb");
f.write(base64.b64decode(i[1]))
import base64
import os
x = open("1.xml", "r")
str = x.read()
r = re.compile(r'<pkg:part pkg:name="[^"]*\/([^"]*)".*?>\s*<pkg:binaryData>((.|\n)*?)<\/pkg:binaryData>', re.S)
m = re.findall(r, str)
os.system("mkdir files")
for i in m:
print "write %s" % i[0]
f = open("./files/" + i[0], "wb");
f.write(base64.b64decode(i[1]))