easy_install.py, 一个简单的Python的json序列号和反序列化库, 同时记录第一次发布PyPi包

这两天因为一个很小的项目,又把有段时间没用的Django捡起来了

虽然Django让很多有洁癖的Pythonist很不爽,但是Django最大的优点也差不多是仅剩的优点就是强大的管理后台自动生成功能。对一些web前端界面不重要甚至根本没有的项目,这个优点很好很强大。

因为需求关系,需要将Django数据库查询的结果json化输出,所以又翻了下Python的json库。

不过因为Django的ORM是紧耦合的,使用Django自带的django.core.serializers来序列化产生的格式也不适合,找了一下也没找到方便自定义的方法。使用Python的json库使用起来也繁琐不方便,需要定义一个有点复杂的serializer函数并且和Django的ORM一起用有点不适应。也因为我的需求比较简单,觉得做一个满足自己需求的json包是很简单的事情,所以就动笔写了。

项目名称easy_json.py

写好后其实没几行代码,但是在我的使用场景中比json.dumps和Django自带的json库方便太多了,所以打算放到PyPi上共享。其实这才是本文的主题,记录我第一次发布Python包到PyPi上 :)

根据网上找的和我自己实践的结果,需要先在项目目录下新建一个setup.py文件,里面的结构大致如下:

from setuptools import setup, find_packages

setup(
    name='easy_json',
    version='0.0.1',
    keywords=('json', 'easy_json', 'simple', 'Django'),
    description='a python json serialize/deserialize library which is easy to use',
    license='MIT License',
    install_requires=[],
    author='zoowii',
    author_email='1992zhouwei@gmail.com',
    packages=find_packages(),
    platforms='any',
)

注意不能随便增加属性,否则很可能验证失败无法上传到PyPi上。

然后可以到https://pypi.python.org/pypi 右侧的注册链接注册一个PyPi帐号,注意密码要求是16位以上,反正密码弄复杂点就没错了。

然后terminal中cd 进入项目目录。执行 python setup.py sdist 进行打包。

然后执行python setup.py register进行登录,会让你选择是使用现有帐号还是新建帐号

然后执行python setup.py upload,等一下就上传成功了。

其实过程非常简单,只不过记录一下以纪念第一次发布PyPi包而已。

附:

Scheme语言的干净宏怎么实现

在lisp中,宏的实现有一个小问题,就是宏展开后的结果如果使用了一些符号,这些符号如果与程序上下文中已有的符号重复会产生冲突,因此自然需要避免这个问题了。

如果是Lisp实现本身可以避免这个问题,就称作干净宏,否则就是不干净的宏。

common lisp的宏是不干净的,而scheme标准中宏是干净的。

先不管common lisp,本文只说怎么使得在实现scheme的宏的过程中,实现一个干净的宏系统。

简单的实现方式就是在展开宏的过程中,执行宏的展开逻辑的代码时,里面的临时symbol都自动替换成一个从来没有使用过的符号。如此一来,自然避免了符号冲突。

PHP实践经验总结一

本文记录我在使用PHP的过程中的一些实践经验,主要做备忘用,因为我用PHP不常用,担心忘掉很多东西。如果某个主题比较复杂,会另开新文记录。(虽然PHP只作为偶尔用的语言,花时间应该不多)

  • PHP文件,如无必要,PHP文件最末尾不要加上一个多余的?>,这主要为了避免在末尾偶然敲错键盘多敲了一些字符。如果加了?>,后面的支付会被当做普通文本输出,难以即时发现码入错误。这也是遵循PSR的建议。
  • PHP作为一个实质上的模板引擎,当然不是只有<?php ?>这一种嵌入PHP代码的方式,因为作为一种模板引擎的话,每次写入<?php echo $hello; ?>太麻烦了,所以PHP支持一种简写的方式:<?= $hello ?>,这样就使用方便了很多,不过PHP默认不支持简写,需要在php.ini中修改short_open_tag=On,这样就支持了
  • 建议使用composer管理PHP的包依赖,因为PHP本身的文件include, require的方式太不优雅了,不利于管理代码结构,所以如果不是使用一些开源完整的MVC/CMS框架,而是自己从头开始写的话,建议使用composer + PHP namespace管理代码结构,在这个基础上去使用一些MVC框架,比如joomla framework(这是一个MVC框架,不是Joomla CMS系统)。
  • 推荐一个PHP解析HTML文本的库,http://simplehtmldom.sourceforge.net/,非常好用,在我用PHP简单爬取一些网页内容的时候帮助很大。
  • 因为nginx的rewrite规则和apache2的rewrite规则不一样,而很多目前已有的系统中大多支持apache 的rewrite规则(使用.htaccess文件),所以建议依然使用apache2作为PHP网站的服务器,另外nginx作为前端反向代理以及server静态资源。
  • 推荐PHP网站的静态资源固定放在一些文件夹下,比如/static, /media,使得代码结构类似于:/static/js, /static/css, /static/img, /media这样,这样的话,在nginx中可以只需要提供一两个规则提供对/static, /media目录下的访问,将来需要增加一些比如/static/fonts目录的时候也不用改web服务器的配置,并且通过PHP代码中提供一些例如static_url这类的函数来动态生成静态文件的URL的话,可以很容易在本地静态文件和CDN网络文件切换。而很多常见的直接使用/js, /css, /img的网站文件结构这点上我觉得不太好。
  • 不要用PHP提供websocket, 长连接,comet等服务,PHP这种每个请求一个进程(线程)的方式(即使用了FastCGI),承担这类应用不太合适。
  • 阻塞较长时间的服务,尽量不要在一个请求中完成,建议用消息队列提交到后台程序处理,或者用curl异步提交,处理完成后触发一个url通知完成。

通用尾递归优化算法(包括自我尾递归和A->B, B->C, C->A的复杂尾递归的优化)

关于通用的尾递归的识别,请参看我的另一片博文《浅谈尾递归的定义和判定方法》

本文讲述我对怎么对各种复杂的尾递归的通用性优化方法的思考。

因为是思考过程中写的,如有疏漏,非常欢迎指正。

为了方便描述,本文代码使用Scheme Racket,部分地方可能会带有一点Python或者伪代码。

首先给出一个简单的尾递归调用的例子:

(define (foo n)
  (if (> n 20140000)
    (begin (display "foo") n)
      (bar (+ n 1))))

(define (bar n)
  (if (> n 20130000)
    (begin (display "bar") n)
    (foo (+ n 2))))

(display (foo 1234))

这是一个没有意义的例子,因为20130000<20140000,所以始终会输出bar2013000x,不过用来分析尾递归足够了。

上面例子的结果是bar20130002,如果没有实现尾递归优化或者尾调用优化,则会栈溢出。这是一个很简单也很普遍的尾递归的例子,当foo==bar时,这个例子就退化成尾递归调用自身的最简单尾递归的形式。至于更复杂的尾递归情形,与这个例子大同小异,所以本文就用这个例子描述。

在我的另一篇博客中描述了一种基于continuation的计算模型,可以用来实现带有continuation的LISP,所以本文也基于continuation给出两种自己的方法。

第一种方法是基于我另一篇博客中描述的f(env, continuation)=(env2, continuation2)的LISP计算模型的。因为根据我之前的博客《浅谈尾递归的定义和判定方法》,解释器/编译器在执行代码前就可以知道,在函数集{foo, bar}中,其中所有函数都是尾递归的。这样在执行到foo函数中的调用函数bar时,只要将continuation的指针指向bar函数的函数体开头,把bar的参数放入env down之后的新env中即可,得到的env和cont用来进行下一次f(env, cont)=(env2, cont2)。相当于不改变函数本身的结构,而是将函数调用简化成一次类似goto的操作。这其实应该是尾调用优化,不过基于我这种计算模型没有堆栈概念的特点,这种尾调用优化差不多具有了尾递归优化的全部优点。

第二种方法是基于continuation和状态机。将函数foo和bar分成多个单元,为了方便描述,起名逻辑单元(普通代码),分支单元(if判断等),尾递归调用单元(尾递归调用另一个函数的部分),结尾单元(非尾递归调用的函数foo/bar的最后执行的代码)。这种方法就是将foo和bar函数完全改造成一个新的函数,这个函数包括了函数foo和bar的全部逻辑,同时具有函数foo和bar的全部功能,是一种replacement。

下面给出第二种方法的scheme伪代码,主要描述思路和方法,具体完善之后再做:

(define f (state params)
  "state可以是很多个状态,包括开始,结束,处于每个逻辑单元和分支单元的状态等"
  (let* ((current-params params) ;; 当前参数。这两个symbol实际上应该使用特殊的不会被用户使用的名称,比如特殊名称规则,只能被系统使用,不能用户代码中使用的等
         (cond 根据foo和bar中的所有分支单元的逻辑的状态机,接收一个状态,返回下一个状态))
    (return-value nil))
  (while (not (= state "end"))
         (begin
           修改env,清空当前层除函数f中固定的symbol如cond,current-params之外的所有key=>value对。并且把current-params中的值
           根据state执行不同的逻辑单元,或者根据state执行不同的结尾单元并修改return-value
           根据state执行不同的尾递调用单元,主要是调整current-params
           ))
  return-value)

经过这种代码级别的转换后,尾递归调用自然就转换成了一个循环的方式,从而完成了尾递归优化,不再需要解释器或编译器做其他处理,把转换后的代码直接解释执行或编译执行就可以了。

水平所限,难免缺漏。如果发现问题,欢迎指正,如果有我没说明白的,也欢迎邮件联系。谢谢!

基于f(env,cont)=(env2,cont2)的scheme解释器的实现原理

public class Scheme {
 public static (SEnv, SContinuation) f(env, cont) {
  // scheme解释器的逻辑
  if(cont.nextForm() is 加法) {
   return (env, cont执行nextForm()的加法操作后的结果)
  } else if(cont.nextForm() is 函数调用) { // 如果是形如(f (g 123))的调用,nextForm不是f调用,而是g调用
   env.down()
   put 函数调用g的参数到env中
   var newCont = 由cont生成,newCont指向cont中的nextForm()的函数调用体内部
  }
 }
 public static void main() {
  SEnv env = SEnv.init();
  SContinuation cont = SContinuation.initFromCode(code);
  while(cont.notEmpty()) {
   env, cont = f(env, cont)
  }
 }
}

关于这个基于f(env, cont)=(env2, cont2)的计算模型用java实现的demo见https://github.com/zoowii/lisp-continuation。注意,这个demo无法运行,因为没有完成,只是给出代码结构和主要代码,对一些细节没有完善。

Scheme的continuation的实现方式(SchemePy)

在Lisp的一些方言中有一种特别的东西,叫做continuation,尤其是在Scheme语言中。

不过其实continuation也不是lisp特有的东西,其他语言中也有本质上相同或类似的东西,也许不叫一样的名字。只不过scheme中直接把continuation的控制权限交给coder自己,能够由程序员自己控制它。

然后,本文说的是怎么实现continuation的,首先要给出continuation的定义。

以状态机的模型定义计算机的话,可以把一个计算机看做是下面这个函数
f(S) = S, 其中S是计算机状态的集合,f就是计算机的程序逻辑

再具体一点,具体到scheme的执行中去,状态集合S就是scheme的上下文环境(env),接下来要执行的程序(continuation),词法作用域(非必须,以下不考虑)

上下文env就是一个记录名称key=>值的记录,这个值可以是基本数值,也可以是一块内存的指针(地址),不过这不影响本文主题,以下直接把所有env中的值看做是对象的应用,不考虑内存的影响增加复杂度。同时env要有一个记录上一层级的指针,指向另一个env。所以上下文env的结构大致如下(一个list结构,列表的一个节点是一个map对象):

                                       -> parent = null
                                       ->key3 => value3
                                       ->key2 => value2
- parent(default = null) ----------------------------->- -key1 => value1
-  key1=> value1
- key2 => value2
--<-----------------------------------------另一个env
  • continuation就是保存下接下来要执行的代码
  • f 就是一个循环,不断接受env和continuation,执行固定的逻辑,返回结果重新再次执行

然后上面的f(S) = S就相当于
f(env, continuation) = (env2, continuation2)

这个式子应用到图灵计算机中就是:

  • env就是内存中的数据区
  • continuation就是内存中的指令序列,和PC计数器
  • f就是图灵计算机中从指令序列中按PC计数器读取下一条指令,在CPU中执行,对内存数据进行操作(得到env2),然后PC计数器+1(得到continuation2)

简便起见,用一段简单的scheme代码描述上面过程

给一个continuation的大概结构先:

{
    code_list: ..., 一个scheme的列表,运行时可能会动态修改
    current: code_list中的当前位置的ID(当前位置可以是code_list中某个元素,或者一个list中的某些位置)
}

看代码:

(define a 100)
(define (fib n)
  (if (< n 3)
  1
  (+ (fib (- n 1)
     (fib (- n 2)))))
(display (fib a))

解释执行时,最开始的状态是env= {}, continuation={code_list: ..., current: null}

然后开始运行后,比如运行到(define a 100)时,执行之前continuation的current指向这个列表,执行则修改env,并且将current指向下一个位置,结束一次执行。

至于scheme中的call/cc,因为continuation也是一个scheme对象,调用call/cc时把这个对象作为参数传递给调用call/cc时的参数(一个函数g),在g的执行过程中,如果调用了这个continuation(命名cont),则把这个cont替换掉当前的continuation并继续执行

先睡了,下次有空再说详细点

PS:

  1. 关于这个基于f(env, cont)=(env2, cont2)的计算模型用java实现的demo见https://github.com/zoowii/lisp-continuation。注意,这个demo无法运行,因为没有完成,只是给出代码结构和主要代码,对一些细节没有完善。

关于将Django网站迁移到SAE上的问题

因为个人算是业余股民,偶尔会需要看看股市行情,但有懒得打开客户端,有的电脑也没装。晚上直接搜也不好查看个性化信息比如自选股什么的,所以之前有做过一个很简单的股票查看网站,只有自己一个人使用。使用Python3+Django开发,用requests抓取数据,使用gunicorn+sqlite部署到自己的VPS上,虽然目前还是0.0.1版,开发出第一个版本后就没改过了,不过勉强够自己使用了。

不过因为自己的VPS是在美国纽约,速度比较慢,所以考虑到速度的原因,想迁移到国内的云服务上,既省得自己管理,便宜还速度快。

所以今天晚上做了下迁移,首先是使用python2.7替换python3,这个很顺利,除了有几个文件有中午注释,文件开头加上# coding: UTF-8就好了。然后把sqlite转成mysql,python manage.py syncdb再用navicat神器把有数据的业务表迁移过去,导出sql,导入sae phpmyadmin就好了。数据库和python环境迁移成功。

然后是代码了, 因为使用了django1.6 ,所以要手动拷贝sae中没有的库到一个lib文件夹下,
os.path.insert(0, lib文件夹路径)即可。

django settings中的修改,因为使用了dj_database_url,所以在index.wsgi中用sae.const中的mysql的用户名密码等信息给os.environ['DATABASE_URL']赋值即可。

差不多了,上传代码运行,然后报错,在调用requests库时,报conn.sock.settimeout(…)中conn.sock为None。网上一搜是2013年后半年对urllib3库的一次修改造成的BUG,https://github.com/kennethreitz/requests/blame/master/requests/packages/urllib3/connectionpool.py#L307,把第307, 308行的

else:
    conn.sock.settimeout(read_timeout)

中的”else:”改成”elif conn.sock: “即可。

再次运行,成功。

网址:http://stockkit.sinaapp.com/

不过说一句,SAE的静态文件加载太慢了啊,都是几百毫秒的级别,虽然还是比美国VPS快,但还是快得有限啊。

关于easy_install和pip的默认源pypi.python.org被中国大陆以外的局域网隔绝的解决方法

最近在一台windows PC上配置python的环境时发现下载setuptools官方的ez_install.py,并且执行python ez_install.py install安装失败,无法下载。因为有在其他反面这种情况的多次经验,知道是国外的局域网不给力,连接不上中国大陆互联网。

以前我在其他电脑上都是为了加快pip安装速度,使用国内镜像安装,但是现在的问题是默认安装python后连easy_install 和pip都没有,需要先安装它们,而安装它们,如果使用ez_install.py的话网络又不给力,所以要麻烦一点。

首先,去setuptools官网,或者 http://pypi.douban.com/packages/source/s/setuptools/ 这个链接下载最新的setuptools源码,解压后,执行python setup.py install安装。这时候可以发现Python的安装目录中多了一个Scripts文件夹,把这个文件夹加入PATH路径中。

安装好之后,我们发现Scripts文件夹中只有easy_install,而没有pip。而我用pip更习惯。并且easy_install 安装包时也有访问不了pypi源的问题。

easy_install --help一下,可以看到有一个-i (–index-url)的选项,这就是指定pypi源的地方了。使用豆瓣的源试一下
easy_install pip -i http://pypi.douban.com/simple 安装成功,并且速度很快。

然后pip也可以这样安装各种Python库了,比如

pip install flask -i http://pypi.douban.com/simple

但是每次这么指定比较麻烦,可以在%HOME%目录下创建一个.pip的目录,里面创建一个pip.conf的文件,里面假如以下内容:

[global]
index-url = http://pypi.douban.com/simple/

接下来就可以直接pip install 库名称 或者依然 pip install 库名称 -i http://pypi.douban.com/simple 来安装Python库了

另外,github上可以找到一个shadowsocks的工具,原本是Python实现的,不过后来有了很多其他语言的实现比如golang, nodejs等。这个工具可以使用加密的方式访问国外局域网,避免我们的信息暴露出去,并且也可以访问一下原本国外速度很慢的网站

XML文档安全查询的几种方法概述

最简单的就是根据安全策略对结果的XML文档逐个节点遍历,返回有权限查询的结果

另外两种方法

  • 方法一:
    一种是类似数据库的视图方式,首先根据实际数据XML文档或者初始化时定义好XML文档的完整结构,就像定义数据库的物理表一样。对于数据XML文档,可以定义一趟DTD对节点进行约束,比如声明资源名称,权限约束,也可以不这样使用,直接根据现有的XML文档定义下文说的安全策略。对这个数据源XML结构,生成DTD格式或者自定义格式,相当于RDBMS系统中的物理表的结构,也称作物理表。
    然后使用XPATH或者类似CSS Selector的语法定义安全策略,语法规则可能很多条,可能可以根据相互之间的优先级构造一棵树。然后可以根据安全策略中的每个角色(以RBAC,Role Based Access Control模型为例),结合物理表的结构,生成一个视图DTD或者自定义格式,类似于RDBMS中的视图,称作角色视图表。
    在用户查询时,假设是用XPATH查询,或者用户角色或权限后,和上文的角色视图表结合,生成一个临时的视图DTD或者自定义结构,类似于RDBMS中的临时表结构,称作结果视图表。
    最后使用这个结果视图表从数据源中查询数据并返回。

  • 方法二:
    这种方法是在定义安全策略或安全规则后,针对用户查询的XPATH,结合用户的权限和对应的安全规则,重新构造这个查询XPATH,然后使用新的XPATH向数据源查询数据并返回。这种方法同样需要上文中的构造物理表和定义安全规则的步骤。

两种方法孰优孰劣不具体实现也不好说,具体实现也可能会有各种问题,暂时不管。

Mac清理本地备份以清理硬盘空间

mac os x有个很贴心的功能,Time Machine,功能很强大,使用很方便。我一向是外接一个移动硬盘,在连接的时候自动备份的。同时Mac OS X除了外部硬盘自动备份,本地也会同时建立快照,虽然在硬盘空间不够时会自动合并多个日期的备份成一周,一月的,但有时候看到关于本机中备份占了那么大空间还是不爽啊,特别是对本人Macbook Air只有128G空间。所以网上搜了下方法。

找到的最简单的方法就是先关闭本地快照,然后再重新启用(或者干脆就一直关掉了)

sudo tmutil disablelocal 禁用本地快照,同时本地快照会被清空
sudo tmutil enablelocal重新启用本地快照