Archive for the ‘技术’ Category
WSGI初探
此文来自本人原JavaEye博客。原文地址
前言
本文不涉及WSGI的具体协议的介绍,也不会有协议完整的实现,甚至描述中还会掺杂着本人自己对于WSGI的见解。所有的WSGI官方定义请看http://www.python.org/dev/peps/pep-3333/。
WSGI是什么?
WSGI的官方定义是,the Python Web Server Gateway Interface。从名字就可以看出来,这东西是一个Gateway,也就是网关。网关的作用就是在协议之间进行转换。
也就是说,WSGI就像是一座桥梁,一边连着web服务器,另一边连着用户的应用。但是呢,这个桥的功能很弱,有时候还需要别的桥来帮忙才能进行处理。
下面对本文出现的一些名词做定义。wsgi app,又称应用 ,就是一个WSGI application。wsgi container ,又称容器 ,虽然这个部分常常被称为handler,不过我个人认为handler容易和app混淆,所以我称之为容器。 wsgi_middleware ,又称*中间件*。一种特殊类型的程序,专门负责在容器和应用之间干坏事的。
一图胜千言,直接来一个我自己理解的WSGI架构图吧
可以看出,服务器,容器和应用之间存在着十分纠结的关系。下面就要把这些纠结的关系理清楚。
WSGI应用
WSGI应用其实就是一个callable的对象。举一个最简单的例子,假设存在如下的一个应用:
def application(environ, start_response):
status = '200 OK'
output = 'World!'
response_headers = [('Content-type', 'text/plain'),
('Content-Length', str(12)]
write = start_response(status, response_headers)
write('Hello ')
return [output]
这个WSGI应用简单的可以用简陋来形容,但是他的确是一个功能完整的WSGI应用。只不过给人留下了太多的疑点,environ是什么?start_response是什么?为什么可以同时用write和return来返回内容?
对于这些疑问,不妨自己猜测一下他的作用。联想到CGI,那么environ可能就是一系列的环境变量,用来表示HTTP请求的信息,比如说method之类的。start_response,可能是接受HTTP response头信息,然后返回一个write函数,这个write函数可以把HTTP response的body返回给客户端。return自然是将HTTP response的body信息返回。不过这里的write和函数返回有什么区别?会不会是其实外围默认调用write对应用返回值进行处理?而且为什么应用的返回值是一个列表呢?说明肯定存在一个对应用执行结果的迭代输出过程。难道说他隐含的支持iterator或者generator吗?
等等,应用执行结果?一个应用既然是一个函数,说明肯定有一个对象去执行它,并且可以猜到,这个对象把environ和start_response传给应用,将应用的返回结果输出给客户端。那么这个对象是什么呢?自然就是WSGI容器了。
WSGI容器
先说说WSGI容器的来源,其实这是我自己编造出来的一个概念。来源就是JavaServlet容器。我个人理解两者有相似的地方,就顺手拿过来用了。
WSGI容器的作用,就是构建一个让WSGI应用成功执行的环境。成功执行,意味着需要传入正确的参数,以及正确处理返回的结果,还得把结果返回给客户端。
所以,WSGI容器的工作流程大致就是,用webserver规定的通信方式,能从webserver获得正确的request信息,封装好,传给WSGI应用执行,正确的返回response。
一般来说,WSGI容器必须依附于现有的webserver的技术才能实现,比如说CGI,FastCGI,或者是embed的模式。
下面利用CGI的方式编写一个最简单的WSGI容器。关于WSGI容器的协议官方文档并没有具体的说如何实现,只是介绍了一些需要约束的东西。具体内容看PEP3333中的协议。
#!/usr/bin/python
#encoding:utf8
import cgi
import cgitb
import sys
import os
#Make the environ argument
environ = {}
environ['REQUEST_METHOD'] = os.environ['REQUEST_METHOD']
environ['SCRIPT_NAME'] = os.environ['SCRIPT_NAME']
environ['PATH_INFO'] = os.environ['PATH_INFO']
environ['QUERY_STRING'] = os.environ['QUERY_STRING']
environ['CONTENT_TYPE'] = os.environ['CONTENT_TYPE']
environ['CONTENT_LENGTH'] = os.environ['CONTENT_LENGTH']
environ['SERVER_NAME'] = os.environ['SERVER_NAME']
environ['SERVER_PORT'] = os.environ['SERVER_PORT']
environ['SERVER_PROTOCOL'] = os.environ['SERVER_PROTOCOL']
environ['wsgi.version'] = (1, 0)
environ['wsgi.url_scheme'] = 'http'
environ['wsgi.input'] = sys.stdin
environ['wsgi.errors'] = sys.stderr
environ['wsgi.multithread'] = False
environ['wsgi.multiprocess'] = True
environ['wsgi.run_once'] = True
#make the start_response argument
#注意,WSGI协议规定,如果没有body内容,是不能返回http response头信息的。
sent_header = False
res_status = None
res_headers = None
def write(body):
global sent_header
if sent_header:
sys.stdout.write(body)
else:
print res_status
for k, v in res_headers:
print k + ': ' + v
print
sys.stdout.write(body)
sent_header = True
def start_response(status, response_headers):
global res_status
global res_headers
res_status = status
res_headers = response_headers
return write
#here is the application
def application(environ, start_response):
status = '200 OK'
output = 'World!'
response_headers = [('Content-type', 'text/plain'),
('Content-Length', str(12)]
write = start_response(status, response_headers)
write('Hello ')
return [output]
#here run the application
result = application(environ, start_response)
for value in result:
write(value)
看吧。其实实现一个WSGI容器也不难。
不过我从WSGI容器的设计中可以看出WSGI的应用设计上面存在着一个重大的问题就是:为什么要提供两种方式返回数据?明明只有一个write函数,却既可以在application里面调用,又可以在容器中传输应用的返回值来调用。如果说让我来设计的话,直接把start_response给去掉了。就用application(environ)这个接口。传一个方法,然后返回值就是status, response_headers和一个字符串的列表。实际传输的方法全部隐藏了。用户只需要从environ中读取数据处理就行了。。
可喜的是,搜了一下貌似web3的标准里面应用的设计和我的想法类似。希望web3协议能早日普及。
Middleware中间件
中间件是一类特殊的程序,可以在容器和应用之间干一些坏事。。其实熟悉python的decorator的人就会发现,这和decoraotr没什么区别。
下面来实现一个route的简单middleware。
class Router(object):
def __init__(self):
self.path_info = {}
def route(self, environ, start_response):
application = self.path_info[environ['PATH_INFO']]
return application(environ, start_response)
def __call__(self, path):
def wrapper(application):
self.path_info[path] = application
return wrapper
这就是一个很简单的路由功能的middleware。将上面那段wsgi容器的代码里面的应用修改成如下:
router = Router()
#here is the application
@router('/hello')
def hello(environ, start_response):
status = '200 OK'
output = 'Hello'
response_headers = [('Content-type', 'text/plain'),
('Content-Length', str(len(output)))]
write = start_response(status, response_headers)
return [output]
@router('/world')
def world(environ, start_response):
status = '200 OK'
output = 'World!'
response_headers = [('Content-type', 'text/plain'),
('Content-Length', str(len(output)))]
write = start_response(status, response_headers)
return [output]
#here run the application
result = router.route(environ, start_response)
for value in result:
write(value)
这样,容器就会自动的根据访问的地址找到对应的app执行了。
延伸
写着写着,怎么越来越像一个框架了?看来Python开发框架真是简单。。
其实从另外一个角度去考虑。如果把application当作是一个运算单元。利用middleware调控IO和运算资源,那么利用WSGI组成一个分布式的系统。
好吧,全文完
Python闭包再研究
此文来自本人原JavaEye博客,原文地址
前两天写了一篇文章,讲了一下Python的闭包。刚好今天又看到一个小问题,和Python闭包有点相关。顺手记录下来。
如下一段代码
funcs = []
for i in xrange(10):
def bar(n):
return n + i
funcs.append(bar)
print funcs[3](5)
这段代码中,我们期望得到的结果是3+5为8。但是实际得到的结果是什么呢?是14。
14是怎么来的?
反汇编看看:
7 0 LOAD_FAST 0 (n)
3 LOAD_GLOBAL 0 (i)
6 BINARY_ADD
7 RETURN_VALUE
注意i是global。
得到14的原因就是,funcs[3]
这个函数对象获取i的值,是在执行的时候。而i的作用域是global。也就是说,当这个func开始执行的时候,i已经变成9了。那么结果当然等于14了。
从这个结果看,以上代码和下面代码效果是等价的。
funcs = []
for i in xrange(10):
pass
def bar(n):
return n + i
funcs.append(bar)#这句重复10遍
print funcs[3](5)
很无趣吧。那么考虑一下,如果把i丢到闭包来做会怎样?
funcs = []
def foo(m):
for i in xrange(m):
def bar(n):
return n + i
funcs.append(bar)
foo(10)
print funcs[3](5)
很遗憾,结果依然是14.
反汇编代码如下:
9 0 LOAD_FAST 0 (n)
3 LOAD_DEREF 0 (i)
6 BINARY_ADD
7 RETURN_VALUE
唉,只是傻傻的远程访问而已。
“所有的bar代码中,i仅仅只是在closure中的一个引用而已。指向的依然是同一个对象。当这个对象被改变,所有的bar执行的时候获得的值都是修改后的值”。
顺手写了一段JavaScript来测试,发现结果是一样的。也是会全局改变。具体代码如下:
但是用haskell实现了一个,完全符合预期的结果。
main = do
let funcs = [(\n -> n + i) | i <- [0..9] ]
let x0: x1 : x2: x3: xs = funcs
return (x3 5)
返回结果是8。
看来Python对FP的支持还是比不上Haskell这种正统的函数式语言。
个人觉得如果Python要发展FP的话,可以考虑如下解决方案:区别本地变量和闭包变量。当声明函数的时候将闭包变量拷贝一份到本地,同时保留指向原闭包对象的引用。在搜索名字的时候,始终都是先搜索本地变量,再搜索本地闭包变量副本,再搜索全局变量。当需要修改的时候,如果是本地变量就直接修改。如果是闭包变量的时候,将原来闭包真正指向的对象进行修改,同时覆盖掉本地的闭包副本。
这个方法只是暂时的考虑,没有仔细的推敲。乍看之下似乎可以解决问题。
不过,Python毕竟是OO的语言。没有Haskell或者Lisp那种天生的作用域的控制能力。唉。那就用OO的方式来搞Python把。
Python闭包研究
此文来自本人原JavaEye博客,原文地址
其实很早以前就想写这么一篇文章了。一直没有机会。正好今天和同事讨论Python闭包的问题,趁着没遗忘赶快记录下来。以下代码运行的Python版本是2.5。
问题还是那个很经典的问题:如下代码会抛一个错误
def foo():
a = 1
def bar():
a = a + 1
bar()
print a
错误则是:
UnboundLocalError: local variable 'a' referenced before assignment
原因分析,直接上dis模块解析bar的汇编代码。得到以下结果:
12 0 LOAD_FAST 0 (a) 3 LOAD_CONST 1 (1) 6 INPLACE_ADD 7 STORE_FAST 0 (a) 10 LOAD_CONST 0 (None) 13 RETURN_VALUE
可以看到,造成这个异常的结果是LOAD_FAST没有找到local变量。STORE_FAST语句的作用是绑定一个local变量。那么在储存变量之前就先去读,当然是会报错了。可是,明明是a = a + 1。而按照赋值语句先执行右边的规律来看,他应该先去外层的a那里读取值,然后再新建一个local的名字a,把值赋给local的a啊?
原因暂且放下,先看一段能正常执行的代码。
把前面代码中的a = a + 1改成b = a + 1。反汇编得到以下代码。
13 0 LOAD_DEREF 0 (a) 3 LOAD_CONST 1 (1) 6 BINARY_ADD 7 STORE_FAST 0 (b) 10 LOAD_CONST 0 (None) 13 RETURN_VALUE
果然按照原来设想的一样,a在这个地方变成了LOAD_DEREF,变成了访问外围的值,然后和1想加以后,储存在一个本地的变量b里面。
正确的程序和错误的程序的差别就是,错误的里面,a是赋值语句的左边。
这看起来不经心的一个差别,会不会是原因呢?答案是YES!看python的PEP227中的一段话。
If a name is bound anywhere within a code block, all uses of the name within the block are treated as references to the current block.
这句话非常拗口。我换一种通俗的方式来解释一下。模拟一下python编译器的行为。首先编译器看到了a = a + 1这句话,发现这是一个赋值语句。先检查右边,遇到了一个名字叫做a的东西。a是什么?编译器问自己。会不会是一个局部变量?于是编译器就傻傻的找到规则,规则表说:如果一个名字出现在参数声明,赋值语句(左边),函数声明,类声明,import语句,for语句和except语句中,这就是一个局部变量。ok。编译器从头到尾一看,a就在一个赋值语句的左边,那么a是一个局部变量没跑了。于是生成一条记录LOAD_FAST 0。你是局部变量,让你运行快一点。接着,分析完右边分析左边,赋值语句左边一定是一个局部变量,简单,你就在0号位置把,直接生成STORE_FAST 0,把栈顶的值给你。编译器顺利的编译结束。下面轮到虚拟机运行了。虚拟运行到这个语句就犯糊涂了,叫我LOAD_FAST 0。可是0里面什么东西都没有啊。我擦勒。只好报错了。
而第二段代码为什么能够正确执行呢?其实就是因为,编译器在整个代码块里面没有发现有绑定名字给a,也没有发现a是一个global对象,所以,就生成一个LOAD_DEREF 语句,告诉虚拟机,a不在这个里面。到别的地方去找他。
那么这个别的地方究竟是什么地方呢?如果python没有这个一定是局部变量的规则,是不是就能修改了呢?
我们继续分析。
先找到LOAD_DEREF的定义是什么?查看dis这个模块的说明,里面有如下的文字:
LOAD_DEREF(i)
Loads the cell contained in slot i of the cell and free variable storage. Pushes a reference to the object the cell contains on the stack.
大意就是,加载cell[i]到栈顶。cell是一个什么?这时候,联想到Python的CodeObject里面有一个属性叫做co_cellvars.会不会和这个有关?
查了文档以后发现如下定义
co_cellvars is a tuple containing the names of local variables that are referenced by nested functions;
被嵌套的函数引用的局部变量?好奇特的说法啊。真这么神奇?执行下列代码。
def foo():
a = 1
def bar():
b = a + 1
print 'bar cellvars:', bar.func_code.co_cellvars
foo()
print 'foo cellvars:', foo.func_code.co_cellvars
执行结果是:
bar cellvars: () foo cellvars: ('a',)
还真是的,a在bar中引用了,所以被加入到cellvars里面。*需要注意的是,他这里只是把名字放到了cellvar中,也就是说,这个闭包中的对象,依然只是一个引用而已。当这个bar调用的时候,是会顺着引用找到真正的值的。而如果真正的值被修改,在所有的bar里面都会体现。
这个过程是怎么加入的呢?反汇编一下foo的代码:
2 0 LOAD_CONST 1 (1) 3 STORE_DEREF 0 (a) 3 6 LOAD_CLOSURE 0 (a) 9 BUILD_TUPLE 1 12 LOAD_CONST 2 (<code object bar at 0x48f458, file "test.py", line 3>) 15 MAKE_CLOSURE 0 18 STORE_FAST 0 (bar) 21 LOAD_CONST 0 (None) 24 RETURN_VALUE
看到奇特的STORE_DEREF, LOAD_CLOSURE, MAKE_CLOSURE指令。
这三个指令的作用分别如下:
STORE_DEREF(i)
Stores TOS into the cell contained in slot i of the cell and free variable storage.
LOAD_CLOSURE(i)
Pushes a reference to the cell contained in slot i of the cell and free variable storage. The name of the variable is co_cellvars[i]
if i is less than the length of co_cellvars. Otherwise it is co_freevars[i - len(co_cellvars)].
MAKE_CLOSURE(argc)
Creates a new function object, sets its func_closure slot, and pushes it on the stack. TOS is the code associated with the function, TOS1 the tuple containing cells for the closure’s free variables. The function also has argc default parameters, which are found below the cells.
看来是编译器发现foo函数里面有一个嵌套的bar函数以后,就把在bar中引用的局部变量a放到一个cell当中,然后将所有的对象都生成成一个tuple,赋值给bar这个funcobject的func_closure。
为了查看神奇的效果,写下面一段代码运行一下看看:
def foo():
a = 1
def bar():
b = a + 1
return bar
b = foo()
print 'bar func_closure:', b.func_closure
如果这程序按照猜测的结果运行,那么将会返回一个cell的tuple。执行结果如下。
bar func_closure: (<cell at 0x454690: int object at 0x803388>,)
果然不出所料。那么func_closure的作用在文档里面怎么描述呢?
func_closure None or a tuple of cells that contain bindings for the function’s free variables. Read-only
看来这个东东涉及到的是Python的名字查找顺序的问题。先local,再闭包,再global。
详细内容可以参看PEP227里面有这么一句话。
The implementation adds several new opcodes and two new kinds of
names in code objects. A variable can be either a cell variable
or a free variable for a particular code object. A cell variable
is referenced by containing scopes; as a result, the function
where it is defined must allocate separate storage for it on each
invocation. A free variable is referenced via a function’s
closure.
The choice of free closures was made based on three factors.
First, nested functions are presumed to be used infrequently,
deeply nested (several levels of nesting) still less frequently.
Second, lookup of names in a nested scope should be fast.
Third, the use of nested scopes, particularly where a function
that access an enclosing scope is returned, should not prevent
unreferenced objects from being reclaimed by the garbage
collector.
相信看到前面func_closure是readonly,大家一定非常失望。看看别的语言的实现如何。
javascript的版本1。
function foo(){
var num = 1;
function bar(){
var num = num + 1;
alert(num);
}
bar()
}
foo();
这个版本会报NaN。。说明Python的问题Javascipt也有。
那如果说num不声明为var呢?
function foo(){
var num = 1;
function bar(){
num = num + 1;
alert(num);
}
bar()
}
foo();
正确提示2.。
要是Python也有这样的机制好了。。
令人高兴的是,python3里面终于改观了。从语法到底层全都支持了(貌似是一个性质)。
语法上加上了nonlocal关键字。
def foo():
a = 1
def bar():
nonlocal a
a = a + 1
print(a)
return bar
foo()()
正确返回2!!
底层加上了可爱的下面两个函数。
PyObject* PyFunction_GetClosure(PyObject *op)¶
Return value: Borrowed reference.
Return the closure associated with the function object op. This can be NULL or a tuple of cell objects.
int PyFunction_SetClosure(PyObject *op, PyObject *closure)
Set the closure associated with the function object op. closure must be Py_None or a tuple of cell objects.
Raises SystemError and returns -1 on failure.
终于可以操作闭包了。哈哈哈哈。。
其实说到最后,如果python中有种机制能支持匿名代码块就好了。嘿嘿。到此结束。
MacOSX下Python2.5版本的locale的编码问题
此文来自本人JavaEye博客,原文地址
今天更新mercurial的时候遇到了一个问题。
执行hg,结果报错:LookupError: unknown encoding: x-mac-simp-chinese
想到这个问题我以前在用django的时候碰到过,原来以为是django的问题,现在才知道原来是普遍的python的问题。
去hg的源代码里面minirst.py里面看了一下,发现是直接调用mercurial的encoding函数的encoding这个变量。
找到encoding.py里面,
try:
encoding = os.environ.get("HGENCODING")
if not encoding:
encoding = locale.getpreferredencoding() or 'ascii'
encoding = _encodingfixers.get(encoding, lambda: encoding)()
except locale.Error:
encoding = 'ascii'
原来是locale这个模块搞的鬼。。
去locale.py里面看了一下,发现以下代码:
if sys.platform in ('win32', 'darwin', 'mac'):
# On Win32, this will return the ANSI code page
# On the Mac, it should return the system encoding;
# it might return "ascii" instead
def getpreferredencoding(do_setlocale = True):
"""Return the charset that the user is likely using."""
import _locale
return _locale._getdefaultlocale()[1]
尝试执行了一下,直接返回了’x-mac-simp-chinese’
为了了解正确的结果,python2.6 -c ‘import locale; print(locale.getpreferredencoding());’返回结果’UTF-8′.
而UTF-8正是我设置的LC_ALL和LANG的结果。
看来是这个_locale模块搞得鬼。不过_locale啊。看名字就是c写的。为了省力。直接把
if sys.platform in ('win32', 'darwin', 'mac'):
改成了
if sys.platform in ('win32'):
然后顺手搜索了一下locale.py中的_locale,把所有的都改了。
执行hg,一切正常。
顺带搜了一下这个问题python的buglist里面有没有,果然看到了。http://bugs.python.org/issue1276。不过略看了一下,发现python2.5.x被无情的忽略了。看来只能自己hack了。:)。
Python中globals对象的回收顺序分析
此文来自本人原JavaEye博客,原文地址
先提示,本文需要一定的python源码基础。许多内容请参考《python源码剖析》。下面切入正题。
今天在群里有人问了一个问题。形如如下的一段程序。
class person:
sum = 0
def __init__(self,name):
self.name=name
person.sum += 1
def __del__(self):
person.sum -= 1
print "%s is leaving" % self.name
a = person('a')
a2 = person('a2')
这段程序的预期的执行结果应该是”a is leaving”和”a2 is leaving”。但是实际上却出乎意料之外,实际的执行结果如下:
a is leaving
Exception exceptions.AttributeError: "'NoneType' object has no attribute 'sum'" in
<bound method person.__del__ of <__main__.person instance at 0x4a18f0>> ignored
为什么引用的名字不同造成的结果会有这么大的差别呢?
分析表面的原因,是person这个引用被指向了None。那他是不是真的是None呢?
__main__.person
None
Exception exceptions.AttributeError: "'NoneType' object has no attribute 'sum'"
in <bound method person.__del__ of <__main__.person instance at 0x4a18c8>> ignored
看来是真的变成了None了。
初步分析原因,应该是程序在执行结束以后,python虚拟机清理环境的时候将”person”这个符号先于”a2″清理了,所以导致在a2的析构函数中无法找到”person”这个符号了。
但是转念一想还是不对,如果是”person”符号找不到了,应该是提示“name ‘person’ is not defined”才对。说明”person”这个符号还在,那”person”指向的class_object对象还在吗?改变程序为以下格式:
class person:
sum = 0
def __init__(self,name):
self.name=name
person.sum += 1
def __del__(self):
#person.sum -= 1
self.__class__.sum -= 1 #1
#print "%s is leaving" % self.name
a = person('a')
a2 = person('a2')
#1就是修改部分,利用自身的__class__
来操作。运行结果一切正常。
说明python虚拟机在回收的过程中,只是将”person”这个符号设置成None了。这个结论同时带来2个问题:第一,为什么会设置成None?第二:为什么”person”会先于”a2″而晚于”a”被回收?
先来分析第二个问题。第一反应是不是按照字母的顺序来回收?但是马上这个结论被推翻。”a”和”a2″都在”person”的前面。那么难道是根据globals()这个字典的key顺序来回收?执行一下globals().keys()方法,得到以下结果:
['a', '__builtins__', '__file__', 'person', 'a2', '__name__', '__doc__'];
看来的确是这样。
但是为什么是这样?要得出这个结论,看来只有去python源码中找答案了。
大家都知道,python代码在运行的时候,会存在一个frameobject对象来表示运行时的环境。类似于c语言的栈帧,也有点像lisp的函数的生存空间,看起来答案要从frameobject.c这个文件中去找了。
在frameobject.c中发现了一个函数:static void frame_dealloc(PyFrameObject *f)。看来解决问题的关键就在眼前。
在frame_dealloc里面截取了以下一段代码:
Py_XDECREF(f->f_back);
Py_DECREF(f->f_builtins);
Py_DECREF(f->f_globals);//1
Py_CLEAR(f->f_locals);
Py_CLEAR(f->f_trace);
Py_CLEAR(f->f_exc_type);
Py_CLEAR(f->f_exc_value);
Py_CLEAR(f->f_exc_traceback);
原来减少了引用啊。。关于Py_DECREF这个宏,python源码里面的解释是这样的:
The macros Py_INCREF(op) and Py_DECREF(op) are used to increment or decrement reference counts. Py_DECREF calls the object’s deallocator function when the refcount falls to 0;
这么说来,我们就要去找f_globals的析构函数了。f_globals是个什么呢?当然是PyDictObject了。证据么遍地都是啊,比如随手找了一个,在PyFrameObject * PyFrame_New(PyThreadState *tstate, PyCodeObject *code, PyObject *globals,PyObject *locals)这个函数里面有一段代码:
#ifdef Py_DEBUG
if (code == NULL || globals == NULL || !<span style="color: #ff0000;">PyDict_Check(globals)</span> ||
(locals != NULL && !PyMapping_Check(locals))) {
PyErr_BadInternalCall();
return NULL;
}
#endif
PyDict_Check。。。检查是否是Dict对象。好吧,此处略过,直接奔向dictobject.c看看里面的代码。
static void
dict_dealloc(register dictobject *mp)
{
register dictentry *ep;
Py_ssize_t fill = mp->ma_fill;
PyObject_GC_UnTrack(mp);
Py_TRASHCAN_SAFE_BEGIN(mp)
<span style="color: #ff0000;">for (ep = mp->ma_table; fill > 0; ep++) {
if (ep->me_key) {
--fill;
Py_DECREF(ep->me_key); #
Py_XDECREF(ep->me_value); #仅仅只是引用计数减一
}
}</span>
//以下略
哈哈哈。还真是按照key的顺序来一个一个清除的。
不过,怎么又回到了Py_DECREF啊?
看来最终解释这个问题要回到GC上面了。
其实从这个地方也可以看出第一个问题的答案了,为什么是None?
从上面代码可以看出,dictobject对象在析构的时候,仅仅只是将value的引用计数减一,至于这个对象什么时候被真正回收,其实是由GC决定而不确定的。也就是说为什么是None,是因为减一了以后,凑巧GC到了而已。
根据Python本身的文档。
Warning: Due to the precarious circumstances under which
__del__()
methods are invoked, exceptions that occur during their execution are ignored, and a warning is printed to sys.stderr instead. Also, when__del__()
is invoked in response to a module being deleted (e.g., when execution of the program is done), other globals referenced by the__del__()
method may already have been deleted. For this reason,__del__()
methods should do the absolute minimum needed to maintain external invariants. Starting with version 1.5, Python guarantees that globals whose name begins with a single underscore are deleted from their module before other globals are deleted; if no other references to such globals exist, this may help in assuring that imported modules are still available at the time when the__del__()
method is called.
Python不能保证_del被调用的时候所有的引用都有,所以,尽量不要overried类的del_方法。
到此结束。
用命令行编译Objective-C程序
此文来自本人原JavaEye博客,原文地址。
最近在看Objc的开发,虽然Xcode很好用,但是还是不太习惯这种大型的IDE开发。 于是自己尝试着在Terminal下面编译。 先是最简单的一段程序。
#import <Foundation/Foundation.h>
int main(int argc, const char *argv[])
{
NSLog(@"Hello, World!");
return 0;
}
直接 gcc helloworld.m,报错。
错误提示如下
Undefined symbols:
"___CFConstantStringClassReference", referenced from:
cfstring=Hello, World! in ccCxrkF8.o
"_NSLog", referenced from:
_main in ccCxrkF8.o
ld: symbol(s) not found
collect2: ld returned 1 exit status
猜测可能lib的位置不对,于是find了一下“Foundation”这个framework的位置,用以下命令编译:
gcc helloworld.m -L/System/Library/Frameworks/Foundation.framework/
还是一样的错误。
无奈之下man gcc从头看到尾。又去google了一下gcc for macosx。发现有一个-framework的选项。尝试了一下。
gcc helloworld.m -framework Foundation
终于成功了
2010-04-22 19:11:11.643 a.out[8932:903] Hello, World!
总结一下:macosx下面,objc的系统调用都是集成在framework里面的,要想编译的话,就必须用-framework选项将framework包含进来才行。
MacOSX中链接boost库
此文来自本人原JavaEye博客,原文地址。
今天在编译一个用到boost_program_options的库的时候出现了一点点小问题。总是提示说找不到boost_program_options这个库。
去/usr/local/lib下面看了一下,发现原来改名成libboost_program_options-mt.dylib了。搜了一下名字中这个mt的意思。
原来是boost1.33的新特性,就是区分了某些lib是单线程或者多线程的。这个区分导致必须显式的指明使用的是多线程还是单线程的库。
自然mt就是指的多线程,st指的单线程。
改用-lboost_program_options-mt以后,编译ok。问题解决。
如何使用logger将错误信息输出到日志
此文来自本人原JavaEye博客,原文地址
今天在做分析http错误请求处理的时候遇到一个问题:当发生500错误的时候,如何将错误输出到日志当中呢?
搜了一下python的doc,在logging模块中有不起眼的一行代码提供了解决方案。
mylog.error('error!', exc_info=True)
其中,mylog就是Logger实例,当exc_info设置为True的时候,logger就会自动的调用sys.exc_info()函数,将traceback的信息打印到日志中。
求职总结
此文来自本人原JavaEye博客,发表于2009年10月26日。原文地址
好吧, 求职总算告一段落了.
从13号参加第一场笔试, 到24号拿到网易游戏offer, 20天时间的经历, 算是人生一大笔财富了.以后可能就没有这样的机会去体验那种心情了.
写一点总结, 希望能够帮助仍然在水深火热之中的同学和朋友.
一. 职业规划
求职的头等大事, 就是树立一个详细完整有可行性的职业规划.
其实职业规划, 不能是等到求职的时候才去想, 它的作用应该是指导大学4年期间学习方向, 以及指导工作以后的研究方向, 甚至可以决定一生的奋斗方向.所以, 最好在进大学1-2年内就确定自己的职业规划, 为之去努力, 然后在求职的时候, 针对你想进入的公司简单修改.不过这就属于是求职技巧的内容了.
职业规划我个人认为应该包含以下几个部分.1.我想要从事什么行业. 2.我想进这个行业内的哪些公司. 3.我进这些公司以后想要从事什么岗位. 4.我进公司以后应该怎么奋斗. 以上几个部分, 必须有足够能说服自己的理由.说服自己是关键, 别人的意见可以作为参考, 但是一定要有自己的观点.
- 就拿我自己来说吧, 我想从事互联网行业, 因为互联网行业是中国目前发展最快的行业之一, 可能甚至没有之一.
- 我想进的公司有, Google:互联网老大, 不用多说;网易游戏:中国自主研发实力最强的游戏公司, 而游戏行业是互联网行业中产值最大的部分;百度, 阿里巴巴, 腾讯:中国互联网三强, 产值过千亿的公司.
- 我想要从事的岗位是基于Unix或者linux平台的服务器端网络程序开发.因为自己是Open Source的狂热爱好者, 而且类Unix平台的服务器的市场占有率相当高.网络, 是未来的发展方向, 我们的生活必将和网络紧密结合, 以后, 上网这个词一定会消失或者变成纪念, 因为我们无时无刻不在上网.
- 进入公司以后, 我希望在4-5年内做到部门leader, 哈哈!
提过了愿景, 实现情况就是, 我已经满足了前三点, 第四点, 我一定会努力去实现它的.
关于想进的公司, 其实google还是心中永远的向往, 可惜google不招人啊…囧..剩下4个, 网易游戏和阿里巴巴的offer到手, 百度和腾讯被鄙视..恩.可能是骂百度太多败了不少人品吧..呵呵..
在面试中, 总会被不停的问到有关职业规划相关的问题.这个时候, 一个清晰明确的规划就十分重要了.当然, 要针对每个公司修改, 不能把面试google的回答去回答百度的问题, 那样不被刷才怪.
二. 简历
简历有两种, 一种是在网上招聘系统填写的在线简历, 另一种是自己精心制作的, 面试时候面试官会盯着问的简历.
网上申请时在招聘系统内填写的简历, 其实个人感觉不是很重要, 主要是系统用来刷一帮笔试名单用的. 面试官不会看.而且, 就算这一关被刷了, 也可以去霸王笔么.
真正重要的, 是自己精心制作的简历. 简历最好大概2-3页大小, 包括个人信息, 获得奖项, 项目经验等等. 对于偏技术类的岗位, 我认为, 项目经验是最重要的, 因为面试官会不厌其烦的问你项目相关的事项, 而且几乎全部的面试官, 都喜欢问你最不熟悉的那个项目.
关于写什么项目, 其实非常重要. 因为2-3页纸有限, 对于项目多的同学来说, 可能根本不够. 取舍就显得很重要了. 尽量把重要的, 与你所应聘的职位相关的项目写上去, 当然, 写上去的项目, 你的内心必须对这个项目无比熟悉. 因为这个项目就是一个大大的陷阱, 是我们设下的陷阱, 让面试官跟着我们的思路问问题的陷阱. 如果陷阱把老虎引过来了, 自己却没有办法制服老虎, 那么被老虎吃了就是唯一的下场. 当然, 风险大收益也大, 如果这个问题答的好, 面试官那里的得分可是会大大增加的.
另一点关于简历项目的要点就是, 你的简历项目, 必须能够证明你能在一个团队内良好的与同事合作. 也就是说, 一个人参与的项目, 除非巨牛, 不然少写, 突出团队项目就要多写, 而且注意, 不是突出自己在团队中的重要性, 而是作为一整个团队所获得的成绩的项目.
其他关于简历的注意事项, 不一定一个公司一个简历, 如果你把自己所有的精华都写在一个简历里面, 那么拿着这个简历就可以了. 当然, 简历里面不能有错别字啊.
三. 笔试
其实我个人还是比较恐惧笔试的, 因为我不知道笔试要考什么内容, 而面试我大致清楚面试官会问什么, 呵呵.
笔试题大多有以下几种:
- 专业题, 计算机相关专业, 就是数据结构与算法, 树, 链表, 字符串操作居多, 外加一些操作系统, 计算机网络的基本知识. 当然,如果你应聘的是dba, 或者sa, 就有一些特殊的题目, 比如sql啊, unix命令之类的. 语言基本上就是C或者C++, 很多招聘Java的职位, 考的试卷也是C++的.这个始终让我觉得很囧.
- 能力题, 能力题大致包括逻辑题, 智力题等等. 逻辑题比较容易做. 智力题的话, 也只有题海战术了, 网上多搜搜这个公司前几年的考题就一般能够找得到.
其实笔试考的好坏也不是很重要, 如果笔试没有进, 那么你就得把笔试题记住, 然后大胆的去霸王面把! 因为霸王面一定会问你, 你考试的时候觉得做的不好的是什么题? 回来以后有没有考虑过怎么做?
四. 面试
面试我想分开来谈, 呵呵, 因为面试太多, 不同的面试方式都不一样.
不过有几个词是通用的: 诚信, 团队, 本色!
另外, 和面试官要不亢不卑, 态度谦和. 多说谢谢是不会错的.
另外, 面试某公司面前, 应该去了解这个公司的用人原则, 或者他们创始人的名言之类的, 这个会在一定的时候, 收获奇效哦!
五. 专业面试
专业面试, 关键点就是把面试官吸引到你希望他问的方向上面.
当然, 如果你不知道面试官会问你什么问题的话, 那就应该找一家公司做试验, 看看他们的面试官会问你什么. 其他的公司大多数是差不多的. 对我自己来说, 可怜的千橡就成为了我的试验品了.
从试验公司知道了会问的问题以后, 回来立刻投入准备, 必须马不停蹄. 如果面试官问的是基础知识, 回来立刻背概念, 基础知识; 如果问项目, 回来把项目代码看一遍把关键技术要点思考一遍. 当然, 最好2个都做.
事实上这一招很有效, 在整个求职过程中, 我被问了4遍TCP三次握手.. – -!
然后要做的就是每次面试以后, 不断的复习这次面试过程中没有回答出来的问题, 强化记忆. 比如我第一次遇到select和epoll的区别的问题时候没有回答出来, 回来以后详细的看了一下, 后面的面试里面又被问了3次, 就回答的很好了.
最后要强调, 基础是非常重要的, 因为靠准备是不能准备全部的知识的, 只能平时积累了.
面试官最喜欢做的事情, 就是抓着你支支吾吾回答不清的地方问. 所以遇到自己不熟或者忘记的地方, 一定要果断的说, 不知道或者不清楚. 当然, 如果他问的问题全部都不知道, 可能就应该考虑自己知识点是否存在漏洞, 在面试回来就应该好好修补一下.
关于专业面试, 其实功夫还是在场外啊, 面试最重要的也就是把面试官引导到你熟悉的地方, 其他能做的, 就是好好的回答问题吧.
六. 群面
群面, 又叫做无领导小组讨论, 或者是AC面. 就是一帮人坐在一起讨论一个问题, 最后得出结论. 考核组员的合作能力, 处理争执的能力, 以及逻辑思维能力, 细心程度等等. 考察的东西相当的多.当然注意点也非常的多.
原来还以为面技术的不需要过群面, 结果网易游戏还是给了我这个机会让我体验了一把群面的感觉.
关于群面的考核目标里面, 我没有说结论的正确与否也是考核的目标. 因为很多讨论根本就是没有正确结果的, 所以无结果, 或者结果不能让所有人满意是很正常的情况. 大家也不要对群面的结果斤斤计较. 群面, 最重要的是过程.
群面的技巧, 按我的理解, 可以总结为以下几点, 不当出头鸟, 不当末尾兵, 说话平和, 面带微笑, 多多的鼓励人, 不要多说一句废话.
不当出头鸟是因为, 讨论的过程之中, 大家会自发的形成一个小团队, 团队必然有leader, 当然, leader不是这么好当的, 因为leader是面试官关注最多的人, 任意一点表现的不好, 都会导致得分的下降, 甚至不能进入下一轮面试. 所以, 如果觉得自己能力有限, 就不要当leader, 否则必败无疑.
不当末尾兵, 是指尽量不要当总结陈词的人. 因为总结的人对于其总结能力, 逻辑思维能力的要求非常高, 如果因为总结而导致逻辑错误, 或者说话支支吾吾, 等等状况的出现, 都会让面试官给你减分.
说话平和, 是说语气要平缓, 不急不躁, 让人听清为准, 不能像吵架一样语无伦次.
面带微笑, 这个是基本常识了.
多多鼓励人, 就是比如当主持人说, 请大家评价一下某位同学, 或者说, 大家评价一下这次面试的时候, 一定要说好的地方. 比如, 这位同学能力很强, 或者说, 某某同学时间控制的很好, 用正面的眼光去看人.
不说废话, 不是说不要当说话最少的人, 而是说说的话必须在点子上. 当整个队伍都是强人的时候, 很可能你的观点是和别人相同的, 这个时候可能就会觉得很尴尬, 自己的话都被别人说了. 那么, 着眼点就可以放在其他地方而不是结论的得出上. 比如, 提醒大家一些细节的小地方等等. 或者, 调控场面气氛等等.
群面其实还是比较能够测试一个人的性格特点的, 面试的时候什么状况都会出现, 大家记得保持冷静就是了.
最后再次强调, 谢谢不离口, 时刻保持微笑.
七. HR面
HR面往往是面试的最后一关, 一般来说, HR只有否决权没有决定权, 所以只要不犯大错误, 基本上offer就到手了.
HR面试的问题, 一般就是你的人生规划, 你最得意到事情, 最失意到事情等等, 大家到应届生网站上面可以查到一堆到问题. 所有问题到回答总结起来就是一句话, 千万不要和HR交心(借用浩哥的原话, 嘿嘿).
因为HR毕竟还是代表公司的, 如果这个时候放松了警惕, 说了一些不该说的话, 最后失意的也只有自己.
所以, 谈论自己缺点等等的时候, 就放一些无关紧要的缺点, 万金油的回答就是, “我觉得我的缺点是学习的东西偏重理论, 实践经验不足”. 这句话真是屡试不爽啊..大家一定要从中看到WS的地方, 深刻领悟之..
另外, HR面还会看你是不是真的想要去他们公司工作, 如果你面试阿里巴巴, 言必谈及马云曾经说过blahblah, 我想, HR那里你的好感分会大大的增加.
还有一点要注意, HR面的时候, 你得时时刻刻表现出自己是团队的一份子. 比如说你最得意的项目之类的, 就一定要举出一个团队合作的例子, 尽量少搞个人英雄主义, 那是20年前的事情了.
要注意的是, 最后结束时候HR会问有没有问题要问我, 别忘了面试还没有结束, 你还是要表现自己非常想去他们公司, 还是有一个万金油的回答, “如果我拿到offer, 能不能马上过去实习呢”
哈哈, HR面一定要脸皮厚啊, 大家千万记住哦. 这是黎明前的黑暗, 光明就在眼前.
八. 感谢
好吧, 真心的感谢这段时间来给我鼓励, 陪我渡过难忘岁月的朋友们. 你们都是我的好兄弟或者姐妹. 以下感谢名单排名不分先后.
浩哥, mapx, 老万, 张师傅, reck, 老邓, 刘姐姐, 子, 波波, 小胖, 小威等等…以及所有帮助过我, 鼓励过我的人!
再次表示衷心的感谢…肺腑之言啊!
最后, 祝愿所有还在为了自己的理想奋斗的同学们, 加油! 要坚信, 自己一定会成功的.
优化Python的DES加密程序有感
此文来自本人原JavaEye博客,发表时间2009年3月24日,可能有些东西现在看来是过时的,甚至是错误的,不过为了保证原来写的时候的感觉,所以没有修改。只在括号中注明了现在的看法。原文地址
最近信息安全的老师布置了作业。要求实现DES算法。。写了1天,优化了1天。。。小有些心得。。
首先感慨一下DES算法。。真是对人对机器都不友好的算法。。竟然还有诡异的S-BOX操作。。。
第二感慨一下Python对2进制不那么方便的支持。。连bin函数都没有。。虽然3.0有了。。可惜2.5没有。。只能自己实现,一大损失效率的地方啊。
好,接下来说说优化过程。
首先是单线程改多线程。。原来是将明文分成64bit一块的序列,每块单独加密。改成每一个64bit的块开一个线程操作。(这个在今天看来完全搞笑)
单线程加密解密一个1000长度的字符串所需时间大致和多线程加密10000长度的相等。效率提升明显。(提升效率很奇怪,忘了自己当初怎么测试的了)
其次是优化异或过程。原来的异或是整体将字符串转成整数然后再异或,发现效率很低。第一次修改为将2个字符串的每一位单独取出转成整形以后异或,效率提升不明显。后来上课时候突然想到,异或操作其实完全可以抛弃整数。1位的2个字符串的异或操作只有4种,写一个字典,用想要异或的2个字符串作为key,结果作为value。简单实现了一下。
def Xor(s1, s2):
"""
字符串xor
s1 -- 第一个字符串
s2 -- 第二个字符串
"""
data={}
data['0','1']=data['1','0']='1'
data['0','0']=data['1','1']='0'
s=[data[s1[x],s2[x]] for x in range(0,s1.__len__())]
return ''.join(s)
三就是优化10进制转2进制。因为python没有提供 bin 函数。于是自己实现了一个。
dec2bin = lambda n:''.join([hex2bin(x) for x in hex(n)[2:].rstrip('L')]
但是在用profile做测试的时候发现的。程序运行中大量的用到了这个函数。很占cpu时间。经过观察发现主要是2个地方使用很多,第一就是异或操作的时候,因为python提供的异或只能在整数之间进行,第二就是在S_BOX运算的时候。
异或操作已经优化了。不需要10进制转2进制了。。剩下就是S_BOX运算了。经过观察发现,S_BOX里面最大的数也不过 15,于是想到可以穷举所有S_BOX里面的数字的值对应的二进制。更改代码如下。
def easyDec2Bin(s):
"""
简单的10进制转2进制,用于简化s_box操作
s -- 10进制数
"""
data={0:'0000',1:'0001',2:'0010',3:'0011',4:'0100',
5:'0101',6:'0110',7:'0111',8:'1000',9:'1001',
10:'1010',11:'1011',12:'1100',13:'1101',14:'1110',
15:'1111'}
return data[s]
第四步优化也是通过profile发现的。。发现程序运行过程中,大量的时间消耗在一个lambda函数上。
b= ''.join(map(lambda x:data[x],s))
这个函数的作用是生成一个字符串。。尝试着把这个lambda函数改成列表操作。
b= ''.join([data[x] for x in s]
效率提升明显。。。原来一直以为map操作比较快。看来并非如此。难怪Guido大叔要推广列表操作了。。
最后一步优化就是用了psyco模块,将程序变成JIT的。。效率提升很大。。。(现在看来其实这一步是最关键的。自己当初好傻)
最终优化的结果就是,加密解密1个40k的文件,原本需要将近50秒的时间,现在10秒不到就完成了。。。。
效果提升明显。不过总觉得还有提升的空间。。尝试把多线程改成Stackless版本的试试看。。。。希望能有更大的提高 。。。。(这句话现在看来完全是搞笑,看来当时的自己还是很稚嫩啊)