Browsed by
分类:Python

强大无比的脚本语言

从Python到Lua

从Python到Lua

在码农世界里,有几个迷之争论:Vim/Emacs与IDE之争、Tab与Space之争、Windows与Linux(也包括macOS等)之争,诸如此类。任何一个争论都是莫名激烈、动不动就友尽,如果你恨一个人,就把他拖进这些争论吧。

其中最可能流血、最板砖横飞的争论,无疑是语言之争(当然,“PHP是世界上最好的语言”这是毋庸置疑的)。使用某个语言或者不使用某个语言,都可能轻易引发激烈的争吵,需要面对巨大的宗教、舆论压力,因此切换语言对码农来说,几乎就是死生之大事。

最近做了一件让人紧张、脸红、流汗的事:重新改写miniSIPServer产品的业务引擎部分,从Python语言切换到Lua语言。作为熟悉(非精通)Python多年的开发人员,离开自己的舒适区去面对新的挑战,去填新的坑,内心还是蛮纠结的。这个切换工作花费了几个月的时间,排除拖延症导致的时间浪费,如此耗时也是近几年罕见,通常一个复杂点的特性也就几个星期工作量而已。在产品博客上,已经简要描述了这次切换的原因,本文从开发人员视角再补充一些细节。

先泛泛了解一些Python和Lua的共性部分:都是脚本类、解释型语言;都是比较简单、干脆的语法;通常都会应用于粘合性场景。常见的不同之处主要在于:Lua更精干、小巧、速度更快,而Python是通用性、全栈型语言,有更广泛的库支持。Python即可适合开发独立的应用程序、也适合作为胶水语言嵌入到应用中,而Lua基本功能集很小,通常只适合作为辅助型语言使用,不太适合作为独立应用的开发语言使用。

从上面这些简要描述来看,Python语言的应用领域要比Lua广泛得多。实际上简单搜索一下可以发现,Lua仅仅在网络、游戏等领域才有一些应用范例。

离开具体应用场景来谈论语言的优劣就是耍流氓,在考虑、考察了很久以后,最终还是重新选择了Lua,这个决定过程是逐步养成的。

我们自己在设计、开发产品时,总是会狂热地追求简单、精致、高效等特性,从产品名称也可以看出我们对『小巧』的迷恋。将Python及支持库打包进安装包,有数MB之大,而Lua仅仅200KB而已,完成整体打包后,体积能缩小1/3,用户下载我们应用程序的时间相应也能减少1/3!这点从一开始就很吸引我们。

然而最初我们选择的是Python,因为Python的库实在太方便了!例如其中的smtp库、xml库等,在我们产品初期的确帮了大忙。而随着我们自己逐步在MSS的核心自行开发了这些基础库,重新审视后发现:Python的库对我们的产品不再是必须的了。这也就是我们重新思考的一个触发点。

如果说Python是屠龙刀,那Lua就是铅笔刀。如果仅仅是削铅笔,那显然铅笔刀比屠龙刀更合适。对于我们而言,应用脚本语言更多的是考虑对核心功能的封装,以及基于此编写业务逻辑脚本,以脚本语言的动态性适配客户需求的多样性。因此在核心功能完备的情况下,脚本语言越轻巧越好,越简单越好,Lua太适合这种场景了。

Python另外一个让人比较诟病的地方就是GIL。由于GIL的存在,Python无法实现真正的多线程。如果用一个Python实例来满足多种业务的需求,一旦其中一个业务出现问题,整个Python虚拟机可能会锁死,导致整个系统崩溃。如果采用多个Python实例,Python的VM实在太重了,对系统的设计、实现、工作负荷都会造成很大的困扰。

而在这点恰恰发挥出Lua的优势。Lua的VM非常小巧,以至于可以毫不犹豫地启动多个VM来实现业务。实际上,我们最终实现为『一个业务启动一个Lua的VM』。具体设计、实现时不用再考虑资源锁死的问题,而且Lua提供的coroutine也很棒,很多场合下可以轻松满足高性能的要求。启动多个Lua的VM带来的最大好处就是:系统的稳定性有了进一步的提高。事实上相当于每个业务都处于沙盒之中,一旦某个业务出现异常,其他业务可以完全不受影响,即使是对已出问题的业务实例,将沙盒(VM)抹掉即可。单纯这一个特点就足以让我们决定放弃Python,改用Lua了。

而Lua带来的第二个好处是『热更新』特性。在运营级、大型企业级软件系统中,『热更新』是非常重要的特性,可以确保整个系统尽可能地稳定运行。而由于每个业务都由独立的VM运行,因此我们可以做到动态修改业务脚本。实际上,我们在开发新业务引擎时,常常在保持MSS运行的情况下,修改脚本测试各种业务逻辑,效果良好。

Lua比Python简单很多,不做特殊优化,Lua的速度比Python要高出很多。我们在开发测试过程中发现,采用Lua脚本对系统的性能影响不大,比Python要节省资源。MSS作为一个高性能SIP服务器,核心采用C、C++实现,而某些处理过程也有客户化、定制化的需求,更关键的是也存在『热更新』的需求,因此我们甚至希望在考察Lua业务引擎效果的基础上,考虑替换MSS呼叫核心部分的处理逻辑,用Lua实现部分呼叫引擎。

因为简单,所以我们可以比较随意地直接修改Lua的代码,以便满足我们应用场景的一些特殊需求。而面对Python,我们可不敢这么做。

当然Lua也有明显的缺点,『简单』在某种程度上是『简陋』的同义词。很多情况下需要扩展一些功能给Lua使用,另外就是Lua也存在不少的坑,即使是Python和C/C++老手也需要小心翼翼。

最后,老话说得好:“结合应用谈语言”。没有完美的语言,只有适合你应用的语言。当你不知道该怎么选择时,选择Python、Java这类通用型语言基本无风险,正如我们在项目早期作出的选择一样,在当时都是非常正确的。只是我们要与时俱进而已。

python的小坑:%u

python的小坑:%u

最近在开发过程中,有一小处代码需要将一个无符号32位整数转成字符串,于是想当然地按照C格式化的方法,采用了类似写法:

print "a=%u"%-1

本来预期是输出内容为:

a=4294967295

实际结果并没有转化为无符号整数,仍然采用了有符号整数方式:

a=-1

作为一名C/C++程序员,对这个结果自然感到惊讶。很显然,python把%u和%d等同起来了。于是搜了一下python的文档,在字符串格式化章节中有这样的描述:

%u Obsolete type – it is identical to 'd'.

根据这文档中的内容,又进一步找到了PEP-0237,其中又强调了这点:

this means that '%u' becomes an alias for '%d'.  It will eventually be removed.

虽然不是很理解为什么采取这种做法,但是很明显,在python代码中不应该再使用%u了,这个格式转换完全等同于%d,而且在后续的版本中有可能会被删除掉。

在空闲之余,又挖了一下python的代码,的确也反应了上述各项说明:

  • PyString_Format 函数中,’iduoxX’都按照 PyInt 处理;
  • 接着在 formatint 函数中,明确进行以下转换:
  • if (x < 0 && type == 'u') { 
        type = 'd'; 
    }
Debian 7与PyCharm3.1

Debian 7与PyCharm3.1

PyCharm无疑是相当杰出的Python IDE(而且提供免费的社区版本),可以运行在Debian7系统中。安装、运行都非常简单,下面简要说明步骤。需要说明的是,全部采用root用户进行操作。

step1:下载PyCharm

目前最新的版本是V3.1,直接从官网下载即可:

cd /opt
wget http://download-ln.jetbrains.com/python/pycharm-community-3.1.tar.gz

step2: 解压缩PyCharm

tar -xzf pycharm-community-3.1.tar.gz

step3: 创建软链接

ln -s /opt/pycharm-community-3.1/bin/pycharm.sh /usr/bin/pycharm

完成上述命令后,在console中直接输入pycharm即可启动。当然,也可以创建图形快捷方式: 右键点击“kickoff应用程序启动器”,选择“编辑应用程序”,选择一个菜单栏(例如“开发”)创建新的菜单项,指向pycharm即可。

2014-03-12 更新:

Kubuntu系统默认没有安装jdk,因此无法运行pycharm,需要先使用以下命令安装jdk:

sudo apt-get install default-jdk
使用python编写简单的守护进程

使用python编写简单的守护进程

server程序与桌面程序最基本的差别在于:server程序通常需要更加稳定地运行,最好是永远都不会中断,确保服务的持续性。

要做到这点,首先当然应当提高程序本身的稳定性,程序本身必须足够稳定才有意义。

然而,天有不测风云,无论多简单的软件,总是会有bug,会有可能导致程序crash。这种情况下,仅仅依赖提高程序本身的稳定性是不够的。我们还需要另外的手段来保证服务的持续性。

最简单的办法就是用守护进程监视当前程序,一旦发生异常或者crash,就重新启动程序。使用python就可以简单地做到这点。
例如,我们通过python脚本(start_mss_app.py)来监视msscli服务程序的运行:

#start_mss_app.py -- run and monitor msscli application

import os
import sys

sys.path.append("./")

def monitor(appName):
    pid = os.fork()

    if 0 == pid:	# child process
        os.system(appName)
        sys.exit(0)

    else:  # parent process
        os.wait()

if __name__ == '__main__' :
    while 1:
        monitor('./msscli')

运行时很简单,使用命令 python start_mss_app.py & 即可。

当msscli程序发生异常导致crash或者退出时,python脚本会自动重起msscli程序。

在进行系统维护或者升级时,我们也有必要强制关掉msscli服务程序,此时由于上述守护进程的存在,我们先必须kill掉守护进程,然后才能kill掉msscli服务程序:

sudo killall python

sudo killall msscli

Django book 2.0

Django book 2.0

最近的项目中需要开发web services,目前主要考虑两个框架: Raby on Rails和Django。网络上比较推荐RoR,但是考虑到我们目前已有的项目和技术积累,我更偏向Python一些,因此更关注Django。

网络上有免费的Django入门教材:

http://www.djangobook.com/

其中文翻译为以下网址:

http://djangobook.py3k.cn/2.0/

Python中面向对象的一点概念

Python中面向对象的一点概念

Python的面向对象和C++颇有一些差异,主要体现在父类和子类的关系、函数重载、虚函数等方面。基本上可以说,在Python的内心,闪烁着C的光芒。

这样说可能太文绉绉了些,看下面这段代码,就能了解到一些基本的概念了:

子类的实例需要调用父类定义的函数时,必须在子类中重定义父类函数。
class A(object):
def __init__(self):
print “a::__init__”
def func1(self):
print “a::func1”
self.func2()   #注意:func2在A中没有定义!
class B(A):
def __init__(self):
super(B,self).__init__() #子类不会自动调用父类的构造函数,需要显示定义
print “b::__init__”
def func1(self):
print “b::func1”
super(B,self).func1()
def func2(self):
print “b::func2”
bObj = B()
bObj.func1()
用Python扩展C/C++程序的小技巧

用Python扩展C/C++程序的小技巧

通常都是用C/C++来扩展Python应用。而在我们程序中是反过来的,核心是C/C++程序,嵌入Python来提供扩展脚本,满足业务开发的需要。

这种模式下,很难应用现有的一些IDE工具来查错和调试。通常我们总是通过输出打印信息来检查python脚本逻辑是否出错。但是对一些语法错误,由于没有IDE工具辅助,反而不易排查,经常阻碍开发进度。后来发现可以应用traceback模块来迅速定位出错的地方,这可真是个很不错的模块。具体实现如下:

def TraceError(self):

exc_type, exc_value, exc_traceback = sys.exc_info()
exc_list = traceback.format_exception(exc_type, exc_value,exc_traceback)
exc_len = len(exc_list)
index = 0

while exc_len > 0:
self.Trace(exc_list[index])  # 自定义输出函数
index += 1
exc_len -= 1
return