作者存档: luojiesi - 第2页

python中yield的实现

Python中的iter可是个好东西,时常都会要用到,不过《Python源码剖析》中只提到了for中的iter,对另一种形式,也就是包含yield語句的函数未做研究。这篇文章就讲讲yield是如何实现的。

其实最后的实现并不困难,但实现yield的部分从源码中找出来可费了我好大功夫。先来看看要研究的范例代码:

#!/usr/bin/python
#encoding:utf8

def iter_func():
  4           0 LOAD_CONST               0 (<code object iter_func at 0xb789a410, file "mod1.py", line 4>)
              3 MAKE_FUNCTION            0
              6 STORE_NAME               0 (iter_func)

    i = 5
  5           0 LOAD_CONST               1 (5)
              3 STORE_FAST               0 (i)
    yield i
  6           6 LOAD_FAST                0 (i)
              9 YIELD_VALUE
             10 POP_TOP
             11 LOAD_CONST               0 (None)
             14 RETURN_VALUE        

for i in iter_func():
  9           9 SETUP_LOOP              22 (to 34)
             12 LOAD_NAME                0 (iter_func)
             15 CALL_FUNCTION            0
             18 GET_ITER
        >>   19 FOR_ITER                11 (to 33)
             22 STORE_NAME               1 (i)

    print i
 10          25 LOAD_NAME                1 (i)
             28 PRINT_ITEM
             29 PRINT_NEWLINE
             30 JUMP_ABSOLUTE           19
        >>   33 POP_BLOCK
        >>   34 LOAD_CONST               1 (None)
             37 RETURN_VALUE

第一眼看到这个编译出来的字节码觉得没有什么滑头,只有一句是不了解的:yield_value,于是认为实现iter估计就是在这句里面了,于是看看yield_value中python虚拟机都做了什么:

...
		case YIELD_VALUE:
			retval = POP();
			f->f_stacktop = stack_pointer;
			why = WHY_YIELD;
			goto fast_yield;

...

fast_yield:
	if (tstate->use_tracing) {
		if (tstate->c_tracefunc) {
			if (why == WHY_RETURN || why == WHY_YIELD) {
				if (call_trace(tstate->c_tracefunc,
					       tstate->c_traceobj, f,
					       PyTrace_RETURN, retval)) {
					Py_XDECREF(retval);
					retval = NULL;
					why = WHY_EXCEPTION;
				}
			}
			else if (why == WHY_EXCEPTION) {
				call_trace_protected(tstate->c_tracefunc,
						     tstate->c_traceobj, f,
						     PyTrace_RETURN, NULL);
			}
		}
		if (tstate->c_profilefunc) {
			if (why == WHY_EXCEPTION)
				call_trace_protected(tstate->c_profilefunc,
						     tstate->c_profileobj, f,
						     PyTrace_RETURN, NULL);
			else if (call_trace(tstate->c_profilefunc,
					    tstate->c_profileobj, f,
					    PyTrace_RETURN, retval)) {
				Py_XDECREF(retval);
				retval = NULL;
				why = WHY_EXCEPTION;
			}
		}
	}

	if (tstate->frame->f_exc_type != NULL)
		reset_exc_info(tstate);
	else {
		assert(tstate->frame->f_exc_value == NULL);
		assert(tstate->frame->f_exc_traceback == NULL);
	}

	/* pop frame */
exit_eval_frame:
	Py_LeaveRecursiveCall();
	tstate->frame = f->f_back;

	return retval;
}

这代码和我想象中的可不太相同,yield_value好像就pop()了一个值(范例中是5),将它设为返回值,再设置why为why_yield,就路到了fast_yield段,而在fast_yield段,由于python初始化线程环境和帧时,tstate->use_tracing和frame->f_exc_type分别为0和NULL,也就是说基本上yield_value就像return一样,直接返回了一个值就跳出了那个大大的switch和外层的for(;;)循环。这就奇怪了,如果返回是5的话,那在call_function的后一句get_iter肯定会出错啊,不仅是返回5不行,返回一个函数也不对,因为函数和tp_iter也为空。

python总要知道iter_func是一个iter这程序才能正常进行下去啊,到底是在哪里标识的呢?make_function 0,call_function 0这两句创建和调用代码和平常的函数一模一样。我又把范例程序里的yield i改成了return i,也没有看出什么来。又一次地仔细检查call_function才发现问题(其实如果早点写个pycparser再查看编译出来的code对象应该就发现了)。

很容易看出来,范例中call_function会走入fast_function通道,而在fast_function中,co->co_flags并不满足条件,因而会调用到PyEval_EvalCodeEx(),如下:

static PyObject *
fast_function(PyObject *func, PyObject ***pp_stack, int n, int na, int nk)
{
	if (argdefs == NULL && co->co_argcount == n && nk==0 &&
	    co->co_flags == (CO_OPTIMIZED | CO_NEWLOCALS | CO_NOFREE)) {
	        /* do something */
	}

	return PyEval_EvalCodeEx(co, globals,
				 (PyObject *)NULL, (*pp_stack)-n, na,
				 (*pp_stack)-2*nk, nk, d, nd,
				 PyFunction_GET_CLOSURE(func));
}

奥秘就隐藏在这个PyEval_EvalCodeEx中。


PyObject *
PyEval_EvalCodeEx(PyCodeObject *co, PyObject *globals, PyObject *locals,
	   PyObject **args, int argcount, PyObject **kws, int kwcount,
	   PyObject **defs, int defcount, PyObject *closure)
{
	register PyFrameObject *f;
	register PyObject *retval = NULL;
	register PyObject **fastlocals, **freevars;
	PyThreadState *tstate = PyThreadState_GET();
	PyObject *x, *u;

	if (co->co_flags & CO_GENERATOR) {
		/* Don't need to keep the reference to f_back, it will be set
		 * when the generator is resumed. */
		Py_XDECREF(f->f_back);
		f->f_back = NULL;

		PCALL(PCALL_GENERATOR);

		/* Create a new generator that owns the ready to run frame
		 * and return that as the value. */
		return PyGen_New(f);
	}
}

这下终于发现了,原来python通过 code对象的co_flags来知道这是一个iter,然后根据已经创建的frame来得到一个iter对象。

PyObject *
PyGen_New(PyFrameObject *f)
{
	PyGenObject *gen = PyObject_GC_New(PyGenObject, &PyGen_Type);
	if (gen == NULL) {
		Py_DECREF(f);
		return NULL;
	}
	gen->gi_frame = f;
	Py_INCREF(f->f_code);
	gen->gi_code = (PyObject *)(f->f_code);
	gen->gi_running = 0;
	gen->gi_weakreflist = NULL;
	_PyObject_GC_TRACK(gen);
	return (PyObject *)gen;
}

总结一下,call_function 0这一句就得到一个iter对象,然后压栈,这样在后面的get_iter中就对这个iter对象进行处理。iter对象就是对 frame的一个简单的包装,回想一下get_iter,它要调用对象的type对象中的的tp_iter操作。而PyGen_Type中的tp_iter被设置为PyObject_SelfIter,简单地返回自身。而接下来的for_iter则会调用对象的type中的ip_iternext。

PyTypeObject PyGen_Type = {
	PyVarObject_HEAD_INIT(&PyType_Type, 0)
	"generator",				/* tp_name */
	sizeof(PyGenObject),			/* tp_basicsize */
	/* ... */
	PyObject_SelfIter,			/* tp_iter */
	(iternextfunc)gen_iternext,		/* tp_iternext */
	/* ... */
};

static PyObject *
gen_iternext(PyGenObject *gen)
{
	return gen_send_ex(gen, NULL, 0);
}

static PyObject *
gen_send_ex(PyGenObject *gen, PyObject *arg, int exc)
{
	PyThreadState *tstate = PyThreadState_GET();
	PyFrameObject *f = gen->gi_frame;
	PyObject *result;

	if (gen->gi_running) {
		PyErr_SetString(PyExc_ValueError,
				"generator already executing");
		return NULL;
	}

	/* Generators always return to their most recent caller, not
	 * necessarily their creator. */
	Py_XINCREF(tstate->frame);
	assert(f->f_back == NULL);
	f->f_back = tstate->frame;

	gen->gi_running = 1;
	result = PyEval_EvalFrameEx(f, exc);
	gen->gi_running = 0;

	/* If the generator just returned (as opposed to yielding), signal
	 * that the generator is exhausted. */
	if (result == Py_None && f->f_stacktop == NULL) {
		Py_DECREF(result);
		result = NULL;
		/* Set exception if not called by gen_iternext() */
		if (arg)
			PyErr_SetNone(PyExc_StopIteration);
	}

	return result;
}

调用iter的next函数时可能有很多情况,这里简化了代码只显示最基本的无参调用。从逻辑上考虑一下,在例子中调用iter_func().next()时,func肯定要从上次yield的地方开始执行起。回头再看看yield_value对应的代码我们可以发现一句

f->f_stacktop = stack_pointer;

这句话将iter对象所对应的frame的f_stacktop置为当前的栈顶。这个f_stacktop是干什么的呢?再在ceval.c里面往上找找就可以发现这样一句

f->f_stacktop = NULL;   /* remains NULL unless yield suspends frame */

因而猜测f_stacktop保存着一个指针,当frame执行的时候,如果它不为空,那将栈顶指针初始化为f_stacktop,而什么情况下需要frame需要在一个不为空的“环境”(就是不按正常的方式在一个空栈上运行)中开始执行呢,答案只有yield。再找找代码发现果然是这样

stack_pointer = f->f_stacktop;

到这里iter对象的next()函数基本就清楚了,而iter的开始、停止等其实也就是调用gen_send_ex。yield的实现基本也就清楚了。

Python GIL在Linux下的实现

《Python源码剖析:深度探索动态语言核心技术》真是好书,讲的深入浅出,语言又风趣。让我有点怨念的就是研究平台是Windows,这一般都没什么问题,不过碰到一些平台相关特性,比如thread时就对不上了。今天看到第15章Python多线程机制,里面对GIL的讲解就全是基于Windows的,自己研究了一下Linux下的源码,做点笔记。

在Python初始化多线程环境这部分Linux和Windows一样,就是把initialized变量设为1,并没有做其它的事情。Linux下的GIL实现有两套,由USE_SEMAPHORES宏决定。由名字就可以看出一种实现是使用信号量,一种是不使用信号量的。主要代码都在Python/thread_pthread.h中。

在Python中,GIL的数据结构为PyThread_type_lock,它是一个指向void的指针,这也是因为不同平台实现不同,无法确定一个具体的类型。对GIL的操作共有4种,分别是PyThread_allocate_lock,PyThread_free_lock,PyThread_acquire_lock,PyThread_release_lock。分别执行创建、销毁、锁、解锁GIL的动作。在Python内部还有一个变量会影响到线程請求GIL时的行为,这就是waitflag,如果waitflag为True,那么线程将被阻塞在請求GIL的操作上,反之非阻塞。下面分别分析两种不同的实现。

一、使用信号量。
使用信号量时的实现相当地简单明了。GIL就是一个信号量(sem_t)。直接上代码(有精简)。

PyThread_type_lock
PyThread_allocate_lock(void)
{
	sem_t *lock;
	int status, error = 0;

	lock = (sem_t *)malloc(sizeof(sem_t));

	if (lock) {
		status = sem_init(lock,0,1);
	}

	return (PyThread_type_lock)lock;
}

void
PyThread_free_lock(PyThread_type_lock lock)
{
	sem_t *thelock = (sem_t *)lock;
	int status, error = 0;

	status = sem_destroy(thelock);

	free((void *)thelock);
}

/*
 * As of February 2002, Cygwin thread implementations mistakenly report error
 * codes in the return value of the sem_ calls (like the pthread_ functions).
 * Correct implementations return -1 and put the code in errno. This supports
 * either.
 */
static int
fix_status(int status)
{
	return (status == -1) ? errno : status;
}

int
PyThread_acquire_lock(PyThread_type_lock lock, int waitflag)
{
	int success;
	sem_t *thelock = (sem_t *)lock;
	int status, error = 0;

        // [1]
	do {
		if (waitflag)
			status = fix_status(sem_wait(thelock));
		else
			status = fix_status(sem_trywait(thelock));
	} while (status == EINTR); /* Retry if interrupted by a signal */

	if (waitflag) {
		CHECK_STATUS("sem_wait");
	} else if (status != EAGAIN) {
		CHECK_STATUS("sem_trywait");
	}

	success = (status == 0) ? 1 : 0;

	return success;
}

void
PyThread_release_lock(PyThread_type_lock lock)
{
	sem_t *thelock = (sem_t *)lock;
	int status, error = 0;

	status = sem_post(thelock);
}

在代码中看到的sem_*开头的函数是符合POSIX的信号量操作函数,可以看到Python其实就是对信号量的操作进行了一下包装,而在[1]处的代码则保证請求锁这个操作不会被信号所中断,看过Unix Networking Programming的应该会对这样的語句很熟悉吧。

二、使用一个自定义结构。这种情况稍微复杂一点点。先来看看在这种情况下GIL是个什么东西。

/* A pthread mutex isn't sufficient to model the Python lock type
 * because, according to Draft 5 of the docs (P1003.4a/D5), both of the
 * following are undefined:
 *  -> a thread tries to lock a mutex it already has locked
 *  -> a thread tries to unlock a mutex locked by a different thread
 * pthread mutexes are designed for serializing threads over short pieces
 * of code anyway, so wouldn't be an appropriate implementation of
 * Python's locks regardless.
 *
 * The pthread_lock struct implements a Python lock as a "locked?" bit
 * and a <condition, mutex> pair.  In general, if the bit can be acquired
 * instantly, it is, else the pair is used to block the thread until the
 * bit is cleared.     9 May 1994 tim@ksr.com
 */

typedef struct {
	char             locked; /* 0=unlocked, 1=locked */
	/* a <cond, mutex> pair to handle an acquire of a locked lock */
	pthread_cond_t   lock_released;
	pthread_mutex_t  mut;
} pthread_lock;

可以看到Python自定义了一个struct,其中的locked属性用来表示GIL是否被锁,lock_released和mut共同来实现解锁和锁操作。为什么要这样做呢?注意代码中的注释,可以看到pthread_mutex在两种情况下的行为是不可以预测的:一个线程再次請求被它占有的锁和請求解锁属于另一个线程的锁。我又找了找这方面的资料(链接),发现pthread_mutex有一个kind属性,它可能是fast,recursive或error checking。不同kind属性的pthread_mutex在上述两个操作下的行为是不相同的,而这几个属性都是NP,也就是Non-Portable的,并不通用。在注释中还提到了设计理念的原因,不过这个我就没有太多研究了。接下来看看具体的对GIL的操作。

PyThread_type_lock
PyThread_allocate_lock(void)
{
	pthread_lock *lock;
	int status, error = 0;

	lock = (pthread_lock *) malloc(sizeof(pthread_lock));
	if (lock) {
		memset((void *)lock, '\0', sizeof(pthread_lock));
		lock->locked = 0;

		status = pthread_mutex_init(&lock->mut,
					    pthread_mutexattr_default);

		status = pthread_cond_init(&lock->lock_released,
					   pthread_condattr_default);
	}

	return (PyThread_type_lock) lock;
}

void
PyThread_free_lock(PyThread_type_lock lock)
{
	pthread_lock *thelock = (pthread_lock *)lock;
	int status, error = 0;

	status = pthread_mutex_destroy( &thelock->mut );

	status = pthread_cond_destroy( &thelock->lock_released );

	free((void *)thelock);
}

可以看到创建和销毁GIL的代码还是非常地直观地,就是分别地创建和销毁其中的lock_released和mut。請求锁和解锁的操作稍微绕了一点点弯,如下。


int
PyThread_acquire_lock(PyThread_type_lock lock, int waitflag)
{
	int success;
	pthread_lock *thelock = (pthread_lock *)lock;
	int status, error = 0;

	status = pthread_mutex_lock( &thelock->mut );
	success = thelock->locked == 0;

	if ( !success && waitflag ) {
		/* continue trying until we get the lock */

		/* mut must be locked by me -- part of the condition
		 * protocol */
		while ( thelock->locked ) {
			status = pthread_cond_wait(&thelock->lock_released,
						   &thelock->mut);
		}
		success = 1;
	}
	if (success) thelock->locked = 1;
	status = pthread_mutex_unlock( &thelock->mut );

	if (error) success = 0;
	return success;
}

void
PyThread_release_lock(PyThread_type_lock lock)
{
	pthread_lock *thelock = (pthread_lock *)lock;
	int status, error = 0;

	status = pthread_mutex_lock( &thelock->mut );

	thelock->locked = 0;

	status = pthread_mutex_unlock( &thelock->mut );

	/* wake up someone (anyone, if any) waiting on the lock */
	status = pthread_cond_signal( &thelock->lock_released );
}

具体每一步各个参数返回什么值可以自己手动拿张纸画画,还是很好推的。直接给出结论:GIL在上述两种情况下的动作分别是:当一个线程试图去锁定已经被它锁定过的GIL时,阻塞(waitflag == 1)或返回失败(waitflag == 0),分别类似于kind属性为fast 和 error checking的pthread_mutex;当一个线程试图去解锁并不是由它锁定的GIL时,马上返回,并成功解锁,类似于kink为fast的pthread_mutex。至此解析完成。

Linux/Win下的翻墙设置

鉴于总被问到怎么翻墙,于是还是写篇文,以后就可以直接留链接了。

浏览网页的时候主要有两个动作需要翻墙,dns解析和实际的http請求。下面分开说。

dns解析翻墙:
主要参考了下这篇文章(好像是需要翻墙的)。
Linux:
看了看代码倒是很简单,原来py里有现成的库拿来用下就可以了,不过默认的dnsserver.conf中的ip在我这还是返回被污染的,于是改成了google的ip,8.8.8.8。再编辑/etc/resolv.conf,改为nameserver 127.0.0.1即可。接下来为了方便使用在/etc/init.d下创建dns-proxy 文件,做成一个服务,内容为

#! /bin/sh
PWD=123456

start()
{
	Num=`ps -ef | grep "^root.*\<python .*dns.py\>" | wc -l`
	if [ $Num -ge 1 ]
	then
		echo "The local dns server is already running"
	else
		echo $PWD | sudo -S python /home/luojiesi/program/dnsproxy/trunk/dns.py &
	fi
	exit 0;
}
stop()
{
	Num=`ps -ef | grep "^root.*\<python .*dns.py\>" | awk '{print $2}'`
	echo $PWD | sudo -S kill $Num
	exit 0;
}

case "$1" in
start)
	start
	;;
stop)
	stop
	;;
restart)
	stop
	start
	;;
*)
	start
	;;
esac

pwd变量改成自己的密码。然后 crontab -e 加一条*/1 * * * * service dns-proxy start 定时检测连接如果断了重连。

Win:
暂空,照那文章应该没难度。

http翻墙:
主要就是架一个ssh -D来翻,其实是 socks代理,不一定是给http服务用,不过意思到了就行了。
Linux:
其实最简单的就是一句话ssh上去就好了,不过为了方便易用还是有点工作要做的,一开始还想复杂了想做个daemon还是用c写的,后来发现根本不用这么麻烦。

首先在.ssh/config下配置一下自己的主机(为了方便),然后再将rsa密钥拷到服务器上去(ssh不像sudo那么好直接从stdin读密码,所以用了key。这一步可能出现问题,如果一切都按网上的教程还是不能用key登陆的话,那多半是服务器sshd的配置和key文件的权限出了问题,可以着重检查一下)。比如我配置文件里翻墙的主机名叫做luojs,那建一个ssh-luojs,内容就一句话

 ssh -N luojs 2>/dev/null 1>/dev/null

-N参数是指只建立通道,不实际执行命令,就比如我用的justhost的主机,没有买ssh服务,其实是不能ssh上去执行命令的,因而不加N我就自动被服务器踢出来了。这里新建文件也就是为了后面好判断一下隧道有没有建立,不建也没有啥关系。照例是/etc/init.d下面建服务,内容和上面基本一样:

#! /bin/sh

start()
{
	Num=`ps -e | grep ssh-luojs | wc -l`
	if [ $Num -ge 3 ]
	then
		echo "The connection has already been established!"
	else
		echo "Connect to jiesiluo.com"
		/home/luojiesi/program/cross-gfw/ssh-luojs &
	fi
	exit 0;
}
stop()
{
	killall -g ssh-luojs
	echo "Stop the connection"
	exit 0;
}

case "$1" in
start)
	start
	;;
stop)
	stop
	;;
restart)
	stop
	start
	;;
*)
	start
	;;
esac

再在crontab -e里改下,搞定了。

Win:
看了好些文章都用到别的软件,什么mytunnel之类,总是有点不爽,记得putty可以完成所有linux下ssh能完成的命令的。仔细研究了一下发现的确是可以的。putty的设置也不截图了,就描述一下。主要有下面几个方面

设置端口转发:在connection-ssh-tunnels里面Forwarded ports里添加一个D7070的端口转发。
使用key登陆:使用puttygen.exe生成密钥,传到服务器上面。本地在connection-ssh-auth 的private key for authentication里选择生成的私钥。
不执行命令: connection-ssh勾选Don’t start a shell or command at all。

不知道怎么的linux下的翻墙感觉总是比win下面快的多,linux下我可以看youtube 1080p的视频而不卡,win下面就。。。我用的翻墙浏览器加插件是 chromium+proxy switchy!,最后附带两个视频广告地址,把它们设置使用一个不存在的代理后看视频广告就没有了。
土豆:http://tdcm.tudou.com/adcontrol/adcontrol
优酷:http://f.youku.com/player/getFlvPath/fileid/*&yad=1&

update: 在win下面将putty换成openssh速度有所提高恩,youtube上勉强能看720p的视频了

ubuntu 启动evince出错

之前折腾pdf查看器,想要个能标功能的,把evince删了又装,結果就出错了:evince: symbol lookup error: evince: undefined symbol: ev_get_locale_dir。查了查居然没有这个错误,后来发现只找了中文网页,囧。再找找就找到了,mark下。
solution here : https://bugs.launchpad.net/ubuntu/+source/glib2.0/+bug/621507

面试感想(二)

今天去面了网易游戏,开始感觉还是不错的,每个人都单独电话通知面试,并且被要求如果不能去面的话要早点告诉他们,好把名额让给别的人。不过今天面完有点囧。

11点到了那,在外面陪GF等了半小时进去等,发现三哥居然面完了被要求再等会,说是还有些要聊的,估计是准备给offer了啊。面试过程感觉好短,就面了20分钟,先说了说项目经历,再讲了讲语言。最后说算法,问我算法会不会,我说不怎么会,结果很囧的事出现了,面试官出了一个非常非常非常 简单的东西,当时我就雷了,这个。。能被称为算法么。接着就出来了。出来一想发现怎么会这样呢,明显就是那种随便被打发了,应该是没有戏了,是从哪里开始的呢?估计只能是语言那一块了,主要问了py的,因为我写的精通py,问了装饰器,reload module会发生什么以及最后一个如果我没有理解错的话应该是instance属性的查找过程。第二个不会,别的都还是比较懂的,可能讲的太杂乱,没有章法了。sigh。。看来以后正式面试不能报着轻松愉快的心态啊。。还是得谨言慎行。。

面试感想(一)

不错不错,今天面完回想一下还是有很多可以改进的地方的。主要是两点:
1. 针对简历巩固知识。
比如今天被问到lucene的搜索过程,居然在merge那一块说不上来了,囧,当时可是看了很久的。
2. 话题要往长处引啊,并备好些说辞,今天是total 失败。不过裸面嘛,自我原谅了!

去除冗余css

话说前端果然是不行啊。。总是被要求抄某个网站的样式什么的,于是总会发现css多了好多,一条条的去除又太麻烦了。于是昨天抽空写了个脚本来处理,现在还很简陋,输入一个html,一个css文件,把css中有用的标出来写入另一个文件。本来还想再处理一下代码,不过要出去玩了,就先丢上来吧。大致的思路是分别解析css文件和html文件,再遍历html节点,和css规则相匹配。

#!/usr/bin/python
#encoding:utf8

import re
import copy
import BeautifulSoup as bs

html_file_name = 'result.html'
css_file_name = 'mobile.css'
class css_rule:

    selecters = []
    raw = ''
    style = ''

    def __init__(self, selecters, raw, style):
        self.selecters = selecters
        self.raw = raw
        self.style = style

def print_css(css_rule):
    if css_rule['used'] > 0:
        for attr in ['selecters', 'raw', 'style']:
            print "%s : %s \n" % (attr, getattr(css_rule['rule'], attr))
        print "used : %d" % css_rule['used']

class html_tag:

    myclass = ''
    name = ''
    myid = ''

    def __init__(self, myclass, name, myid):
        self.myclass = myclass
        self.name = name
        self.myid = myid

def walk_html(tag):
    if not isinstance(tag, bs.NavigableString):
        yield tag
        for i in tag.contents:
            for tmp in walk_html(i):
                yield tmp

def get_format_node(tag):
    return html_tag(tag.get('class', ''), tag.name, tag.get('id', ''))

def rule_match(format_nodes, selecters):
    if not selecters:
        return True
    if not format_nodes:
        return False
    n = format_nodes.pop(0)
    s = selecters.pop(-1)

    if node_match_selecter(n, s):
        return rule_match(format_nodes, selecters)
    else:
        selecters.append(s)
        return rule_match(format_nodes, selecters)

def node_match_selecter(node, selecter):
    name = re.findall('^(\w*)', selecter)
    if name:name = name[0]
    if name and name != node.name:
        return False

    class_name = re.findall('\.([\w]+)', selecter)
    if class_name: class_name = class_name[0]
    if class_name and class_name not in node.myclass.split(' '):
            return False

    id_name = re.findall('#([\w]+)', selecter)
    if id_name: id_name = id_name[0]
    if id_name and id_name != node.myid:
            return False

    return True

def print_node(node):
    for attr in ['myclass', 'myid', 'name']:
        print "%s : %s \n" % (attr, getattr(node, attr))

css_list = []
with open(css_file_name) as css_file:
    css_content = re.sub('/\*[\d\D]+?\*\/', '', css_file.read().lower())
    css_re = re.compile('([\d\D]+?)\{([\d\D]+?)\}')
    for (selecter, style) in css_re.findall(css_content):
        selecter = re.sub('[\n\t]', '', selecter)
        style = re.sub('[\n\t]', '', style)

        selecter = re.sub('\s*,\s*', ',', selecter)
        parent_list = selecter.split(' ')
        #print parent_list
        last = parent_list.pop(-1)
        #print last
        selecter_list = last.split(',')
        #print selecter_list
        map(lambda x:re.sub('[\n\t ]', '', x), selecter_list)

        for a_selecter in selecter_list:
            #if ':' in a_selecter:
            #    (se, st) = a_selecter.split(':')
            #else:
            #    se = a_selecter
            #    st = ''
            nodes = copy.copy(parent_list)
            nodes.append(a_selecter)
            c = css_rule(nodes, "%s{%s}" % (selecter, style), style)
            css_list.append({'rule' : c, 'used' : 0})

with open(html_file_name) as html_file:
    soup = bs.BeautifulSoup(html_file.read().replace('\n', '').lower())
    body = soup.body

    for i in walk_html(body):
        #print i.name, "finished! \n"
        format_nodes = [get_format_node(k) for k in i.findParents() if k.name not in ['html', '[document]']]
        format_nodes.insert(0, get_format_node(i))

        for rule in css_list:
            tmp_rule = copy.deepcopy(rule['rule'].selecters)
            tmp_nodes = copy.deepcopy(format_nodes)
            if rule_match(tmp_nodes, tmp_rule):
                rule['used'] += 1

#map(print_css, css_list)
aaa = open('processed.css', 'w')
for rule in css_list:
    if rule['used'] > 0 :
        aaa.write(rule['rule'].raw + '\n')

aaa.close()

在遍历html树时用了yield,注意下递归时yield的用法,http://www.iteye.com/topic/338111。代码就是随手写写,有些命名规则什么的实在是很囧,要是以后想做成一个成熟的项目的话还得再完善下。

有一个bug:css规则我不是很熟,只考虑了如selecter1 selecter2,selecter3这种用法,试了下后发现样式漏掉了一点,检查后原来css可以这么写selecter1 selecter2,selecter3 selecter4,fix起来也简单,逻辑没有问题就好说了。还有点小问题,比如现在只能匹配一个html文件(这改起来很方便),写入css文件后会有冗余(每行一个规则,再去除一下即可)。

然后用不会用gdb,pdb真是坑爹啊,还是得学,为了debug下专门开eclipse,好麻烦,然后写代码还坑爹,编辑效率太低了。

tmux配置

最近实在是受不了曹楼的网络了,远程到服务器上开服务总是因为网络断开,不过上次用screen又实在觉得不好用,这次试试tmux,发现还不错,发个配置文件,不知道的看 这里。参考了这里,再结合自己的习惯修改了下。


set-option -g prefix C-a
unbind-key C-b
bind-key C-a send-prefix
setw -g mode-keys vi
setw -g xterm-keys on
unbind %
bind | split-window -h
bind h split-window -h
unbind '"'
bind - split-window -v
bind v split-window -v
set-option -g visual-activity on
setw -g monitor-activity on
setw -g automatic-rename off
setw -g utf8 on
set -g base-index 1
set -g terminal-overrides "*88col*:colors=88,*256col*:colors=256,xterm*:colors=256"
set -g default-terminal "screen-256color"
set -g status-utf8 on
set -g status-justify centre
set -g status-bg default
set -g status-left "#[fg=cyan]:#[fg=blue]: #[fg=red][ #[fg=green]#S@#H #[fg=red]]#[default]"
set -g status-left-length 20
set -g mouse-select-pane on
set -g status-right-length 25
set -g status-right "#[fg=red][ #[fg=green]%H:%M #[fg=magenta]%a %m-%d #[fg=red]] #[fg=blue]:#[fg=cyan]:#[default]"
#setw -g window-status-format '#[fg=blue,bold]#I #T#[default] '
#setw -g window-status-current-format '#[fg=blue,bold,reverse]#I #T#[default] '
#setw -g window-status-alert-fg red

bind -n M-c new-window
bind -n M-p previous-window
bind -n M-n next-window
bind -n M-o down-pane
bind -n M-\; command-prompt
bind -n M-d detach-client

我vim里使用了很多F11这样的map,所以切换窗口就不能用了,本身是bind M-Left之类的键的,但发现它本身有修改大小的作用,于是取了个折衷,用M-n/p这样,M-o是在各个pane中切换。M-;是显示命令行提示符,话说在出现提示符后按ESC居然不能取消。再研究研究。M-d是detach。感觉tmux还是很好用的恩。

顺便总是没找到好和terminal啊,上次试用的urxvt,怎么折腾也不能输入中文,网上翻了个遍,shlug,hzlug也问了都没答案。只能先继续 gnome-terminal凑和着用了。。

update: 和vim有冲突啊。。在insert模式下面按ESC,再很快地输入:w,这时候出来的是tmux的command-prompt,有点坑爹。。明明没有prefix发送过去。为啥就被认为是操作tmux呢。。并且我的vim亮黄变屎黄了。。算了,还是单独给vim开个terminal吧,纠结来纠结去快捷键也因为不能冲突而弄的很麻烦。一点都不快捷了。。

Android下的vpn管理软件(一)

前几天晚上趴床上玩手机,每次上vpn的时候都要输入密码,觉得太麻烦了。懒惰使人进步嘛!上网找了一个多小时,想找一个管理vpn的,发现居然都没有,就找到一个one vpn,收费3刀,rmb的话是15元,当时还觉得奇怪,这么简单的功能,为什么都没有呢,于是想自己写一个。后来发现这种情况不是没有理由的!已经过去三天了,还是可耻地没写好,惭愧啊!不过思路很明确了,继续看看dev guide和examples熟悉下。

网上都能搜到的就不写了,主要记录下折腾过程。先不用说的,配环境,官网在这,照例问候一下万恶的gfw。下载SDK, ADT plugin for eclipse。添加AVD,跑起hello,world,一切到这里都很顺利。之后先扫了扫develop guide,内容不少,估计完全看完太慢了,我就是想弄个简单的功能,应该不用这么复杂。于是看api里,发现居然没有vpn相关的内容,这下觉得不太对了。官方maillist里搜了搜,发现居然没有公布出来vpn相关的api,要做这方面的内容的话得用几个private package。一通搜索后,找到了隐藏的package,这里

有了javadoc,准备先按这个试试,import了一下,显示not resolved,居然没有在android2.2.jar里面吗,坑爹吗这不是。于是上网找源码,又纠结了会,我只要android sdk的源码哇,找出来好几个都是android的,总共2.6G。。。最后这里找到源码。这里把源码attach到android2.2.jar。源码里果然是有的。但看了看源码后发现不知道它是怎么连接的vpn,连password都没有出现过,连接的函数都是未被实现的,又囧到了。

想想换一条路吧,好像apk文件是可以反编译的,手机里root explorer搜一通后,定位到vpnservices.apk和vpnservice.odex,传到电脑上,附带的还是/system/framework下的那些odex文件。这里把odex转成dex,实际操作时有点出入,先是生成out文件夹时出现java runtime error : bad magic value,搜索未果,在这里 下载1.2.6版的baksmali后解决,然后就是生成out目录时很要几个odex文件,就是之前拷的那几个,具体的看提示吧。然后把dex转成java代码,方法在这里。转出了源代码一看有些不太对劲啊,效果一般般,不过把package名到google code search上一搜,直接就出来了,真是囧。

最后得到两份可能工作的源码,都是在google code search上搜的。package名分别是com.android.settings.vpn和com.android.server.vpn,但每一份都需要自己把sdk源代码中的android.net.vpn放到工程中去,再根据一些错误提示放点另外的类,具体的试一下就知道。然后还有一个很重要的东东,IVpnService.aidl,这东西也是搜出来的,随之一起还有vpnprofile 的一个profile,也放到工程里,eclipse会自动生成两个类。再就是修改一些xml之类,让两个工程没错误。

我个人是倾向于用com.android.server.vpn那个package里的东西来做的,它是定义了一个bindservice,不提供任何界面,需要用户自己去写。com.android.settings.vpn那个就是手机里的那个vpn设置,我非常怀疑one vpn就是在上面改了点代码然后发布出来的,因为界面都一模一样,就是加了个保存密码的勾。不过估计这个设置应该也是和 android一样的apache授权,随便用。先这样吧,再看看develop guide,最近好忙,实在没时间就直接拿第二个来改好了。。。

update:
整理后vpn server的代码
整理后vpn settings的代码

虾米自动签到

罪过罪过。。好久不写。最近用了虾米,歌是挺多,就是觉得网站好乱,下歌也还要积分。我又从来不能坚持登陆的,于是用cron + python写个定时登录脚本。很简单,直接上代码。

#!/usr/bin/python
# encoding:utf-8

import cookielib, urllib2, urllib, StringIO, gzip, os, time
import sys

if len(sys.argv) != 3:
    exit(0)

def get_raw_content(rev):
    if rev.headers.has_key('Content-Encoding'):
        fileobj = StringIO.StringIO()
        fileobj.write(rev.read())
        fileobj.seek(0)
        gzip_file = gzip.GzipFile(fileobj=fileobj)
        content = gzip_file.read()
        fileobj.close()
    else:
        content = rev.read()

    return content

LOG = True
LOG_FILE = os.environ['HOME'] + '/.xiami_signin_log'

SIGNIN_HEADERS = {
    'Accept' : '*/*',
    'Accept-Charset' : 'UTF-8,*;q=0.5',
    'Accept-Encoding' : 'gzip,deflate,sdch',
    'Accept-Language' : 'en-US,en;q=0.8',
    'Connection' : 'keep-alive',
    'Content-Length' : 0,
    'Content-Type' : 'application/x-www-form-urlencoded',
    'Host' : 'www.xiami.com',
    'Origin' : 'http://www.xiami.com',
    'Referer' : 'http://www.xiami.com/',
    'User-Agent' : 'Mozilla/5.0 (X11; U; Linux i686; en-US) AppleWebKit/534.20 (KHTML, like Gecko) Ubuntu/10.04 Chromium/11.0.672.2 Chrome/11.0.672.2 Safari/534.20',
    'X-Requested-With' : 'XMLHttpRequest'
}

LOGIN_HEADERS = {
    'Accept' : 'application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5',
    'Accept-Charset' : 'UTF-8,*;q=0.5',
    'Accept-Encoding' : 'gzip,deflate,sdch',
    'Accept-Language' : 'en-US,en;q=0.8',
    'Cache-Control' : 'max-age=0',
    'Connection' : 'keep-alive',
    'Content-Type' : 'application/x-www-form-urlencoded',
    'Host' : 'www.xiami.com',
    'Origin' : 'http://www.xiami.com',
    'Referer' : 'http://www.xiami.com/member/logout',
    'User-Agent' : 'Mozilla/5.0 (X11; U; Linux i686; en-US) AppleWebKit/534.20 (KHTML, like Gecko) Ubuntu/10.04 Chromium/11.0.672.2 Chrome/11.0.672.2 Safari/534.20',
}

LOGIN_DATA = {
    'done' : 'http://www.xiami.com/',
    'type' : '',
    'email' : sys.argv[1],
    'password' : sys.argv[2],
    'submit' : '登 录'
}
signin_url = 'http://www.xiami.com/task/signin'
login_url = 'http://www.xiami.com/member/login'

TIMEOUT = 100

def do_sign(data):
    success = True
    try:
        opener = urllib2.build_opener(urllib2.HTTPCookieProcessor(cookielib.CookieJar()))
        urllib2.install_opener(opener)
        request1 = urllib2.Request(login_url, data = urllib.urlencode(data), headers = LOGIN_HEADERS)
        rev1 = urllib2.urlopen(request1)

        request2 = urllib2.Request(signin_url, headers = SIGNIN_HEADERS, data = {})
        rev2 = urllib2.urlopen(request2)
        res = get_raw_content(rev2)
    except:
        success = False

    if success and len(res) > 100:
        success = False

    if LOG:
        log_file = open(LOG_FILE, 'a')
        log_file.write('%s %s signin %s\n' % (time.strftime('%Y-%m-%d %H:%M:%S'), data['email'], str(success)))
        log_file.close()

    if not success:
        time.sleep(TIMEOUT)
        do_sign(data)

do_sign(LOGIN_DATA)

存好后再 crontab -e,输入
0 12 */1 * * python /path-to-xiami_signin.py email password