使用rvm进行ruby多版本管理

rvm与Python的virtualenv和Node的nvm类似。使用它可以很方便的在你的系统中安装多个Ruby环境。类似的工具还有rbenv、ry、rbfu等。


安装rvm


以下的命令都是在当前用户权限下执行的,最好不要使用sudo。

下载安装rvm:


$ curl -sSL https://get.rvm.io | bash -s stable

安装完成之后再加载配置:


$ source ~/.profile

为了下次能直接使用,建议将该命令添加到 ~/.bash_profile 或者 ~/.zshrc 中。


使用rvm安装Ruby时会默认从官方网站上进行下载,为了提高下载速度这里建议将安装源修改为淘宝的镜像。


$ sed -i -e ‘s/ftp.ruby-lang.org\/pub\/ruby/ruby.taobao.org\/mirrors\/ruby/g’ ~/.rvm/config/db

使用


安装完成之后就可以使用了,以下介绍几条常用的命令。

列出已知的ruby版本:


$ rvm list known

列出已经安装的ruby:


$ rvm list

安装一个ruby版本:


$ rvm install 2.1.0

这里安装最新的2.1.0版本的Ruby。


如果安装了多个版本,想使用其中一个版本:


$ rvm use 2.1.0

设置为默认版本:


$ rvm use 2.1.0 –default

use了某个版本之后,可以使用 which ruby 命令查看当前的ruby命令信息。


删除一个已安装的版本:


$ rvm remove 2.1.0

更多内容请参考官方文档: https://rvm.io/#docindex

Rack开发简介

Rack是Ruby应用与web服务器之间的一个接口,它在服务器与应用程序之间作为中间件,可以对用户的请求和程序的返回数据进行处理。现在几乎所有主流的Ruby web框架都是支持Rack接口的。


Rack与Python的wsgi很相似,在它的规格书(http://rack.rubyforge.org/doc/SPEC.html)中也说道它采用了WSGI的一些内容。


开始


首先安装Rack:


[sudo] gem install rack

然后通过一个简单的例子来讲解。


require “rack”
rack_app = lambda{|env| [200, {}, [“Hello World!”]]}
Rack::Handler::WEBrick.run rack_app, :Port => 3000

执行上述代码,然后访问 http://127.0.0.1:3000 会看到 “Hello World!”。


上面代码中的rack_app即是一个Rack应用。Rack应用除了lambda之外也还可以使用其他对象,只要满足以下条件即可:

可响应call方法的对象;
接收一个参数rack环境 environment 。它是一个散列表,包含了CGI的信息和rack的一些变量;

* 返回一个有三个值的数组,第一个值为返回状态 status;第二个值为返回头 headers,也是一个散列表;第三个值为返回正文 body,它必须是一个可响应each方法并生成字符串的对象,例如字符串数组。


WEBrick是Handler的一种。Handler用于将web服务器与Rack连接。使用 Rack::Handler.constants 可以查看Rack包含的所有Handler。


请求/Request


在Rack应用中可以直接操作env参数来访问请求信息,但是这种方法不太方便。对于这种操作Rack::Request对象提供了方便的接口。


request = Rack::Request.new env

创建request对象时传入env参数。


响应/Response


Rack应用的返回值是一个有三个值的数组,包含了返回状态、返回头和返回正文。对于简单的程序手动构建数组还行,如果是复杂的程序则要考虑自动构建了。

同样的可以使用Rack::Request对象来创建返回数据。


response = Rack::Response.new
response.finish

内容填充完之后调用response对象的finish方法生成符合Rack规范的数组对象。

平铺式窗口管理器——awesome和i3

最近折腾了一下平铺式的窗口管理器 awesome 和 i3。感觉这两个都很不错,现在进行一下简单的总结。


Awesome


先说一下Awesome吧。安装过程很简单。

对于ArchLinux的用户可以使用 pacman 直接进行安装:


$ [sudo] pacman install awesome

对于LinuxDeepin的用户可以使用 apt-get 进行安装:


$ [sudo] apt-get install awesome

启动


如果是使用登陆管理器,那么在登陆是选择 awesome 即可。


如果没有使用登陆管理器,则在 ~/.xinitrc 脚本中添加 exec awesome


常用快捷键


awesome的快捷键是 $mod 加上其他键。在awesome中 $mod 默认为 Win(Mod4)键,可以通过修改配置文件将其改为其他按键。



  • $mod + r : 运行命令

  • $mod + Enter : 打开一个新终端

  • $mod + Shift + c : 关闭当前窗口

  • $mod + m : 最大化当前窗口

  • $mod + Ctrl + r : 重新加载配置

  • $mod + Shift + q : 退出awesome


  • $mod + j : 切换到下一个窗口

  • $mod + k : 切换到前一个窗口

  • $mod + Left : 查看前一个桌面

  • $mod + Right : 查看后一个桌面

  • $mod + 1-9 : 切换到桌面 1-9

  • $mod + Shift + j : 当前窗口和前一个窗口互换位置

  • $mod + Shift + k : 当前窗口和后一个窗口互换位置

  • $mod + h : 把主区域(master width)的宽度增大5%

  • $mod + l : 把主区域(master width)的宽度减少5%


  • $mod + space : 把当前tag更换为下一种布局

  • $mod + Shift + space : 把当前tag更换为前一种布局

  • $mod + Ctrl + space : 切换当前窗口是否为浮动的

  • $mod + Shift + r : 重绘当前窗口

  • $mod + t : 标记窗口(可标记多个)

  • $mod + Shift + 1~9 : 把标记的窗口移动到第一~第九桌面上

  • $mod + Ctrl + 1~9 : 把当前桌面和1~9桌面同时显示

  • $mod + Esc : 快速切换到上一个桌面


配置


awesome 的配置文件是一个lua脚本,要想自己进行配置可能得稍微了解一下lua语言。


$ mkdir -p ~/.config/awesome/
$ cp /etc/xdg/awesome/rc.lua ~/.config/awesome

I3


同样的i3也可以直接从软件源里进行安装。执行如下命令:


$ [sudo] pacman install i3-wm i3lock i3status dmenu

或者:


$ [sudo] apt-get install i3-wm i3lock i3status dmenu

启动方式与awesome类似。


i3对应的配置文件为 ~/.i3/config ,状态栏的配置文件为 ~/.i3status.conf 。与awesome类似,i3的快捷键也是 $mod 加上其他键。


它的配置文件比较简单。由于不习惯它默认的按键,于是我就修改成了vim风格的按键。我的配置放在了 https://github.com/wusuopu/my-i3-config ,各位感兴趣的可以参考下。


我设置的快捷键如下:



  • $mod + Enter : 打开一个新终端

  • $mod + q : 关闭当前窗口

  • $mod + Shift + q : 退出i3

  • $mod + d : 运行dmenu

  • $mod + e : 运行pcmanfm文件管理器

  • $mod + c : 运行i3lock锁屏

  • $mod + h : 选中左边的窗口

  • $mod + j : 选中下边的窗口

  • $mod + k : 选中上边的窗口

  • $mod + l : 选中右边的窗口


总结


两个都试用了几天,我个人的感受是awesome比较强大,配置文件就是一个lua有脚本,因此可以在配置文件里完成一些比较复杂的功能。但是同时lua脚本作为配置,修改起来比较复杂,需要会一点lua语言。

相对而言i3就比较简洁了,同时功能也会少一些,不过我感觉也够用了。

使用C语言编写Python扩展5——垃圾回收管理

上一节介绍了创建一个具有属性的类,由于对象具有属性数据,因此在进行内存管理时要多加注意。这一节就介绍一下Python的垃圾回收管理。

在Python中垃圾回收主要是靠的计数引用方法,但是单凭计数引用还是不够的。先看看下面这段Python代码。


n = []
m = []
n.append(m)
m.append(n)
del m
del n

如果只靠计数引用的话执行上面这段代码之后n和m都不能被回收,因为它们的引用计算值都不为0。

像上面例子这样相互循环引用称作循环引用垃圾,在Python中有循环垃圾回收器(cyclic-garbage collector)专门用于回收此类计数引用无法处理的垃圾内存。


接着上一节的例子,继续编辑noddy.c


为了让该对象类型支持垃圾回收,将PyTypeObject的tp_flags字段增加Py_TPFLAGS_HAVE_GC这个标志位。同时与GC(Garbage Collection)相关的tp_traverse和tp_clear这两个字段也要设置。



  • tp_traverse是用于垃圾回收器(garbage collector)遍历该实例对象中所有需要回收的属性对象。

  • tp_clear是用于清除内部各个属性对象的。


首先定义tp_traverse和tp_clear所对应的函数:


static int Noddy_traverse(noddy_NoddyObject self, visitproc visit, void arg)
{
Py_VISIT(self->first);
Py_VISIT(self->last);
return 0;
}
static int Noddy_clear(noddy_NoddyObject self)
{
Py_CLEAR(self->first);
Py_CLEAR(self->last);
return 0;
}

Py_VISIT和Py_CLEAR是两个宏,简化了visit操作和clear操作。


然后再修改noddy_NoddyType结构体定义:


static PyTypeObject noddy_NoddyType = {
PyObject_HEAD_INIT(NULL)
0, /
ob_size/
“noddy.Noddy”, /
tp_name/
sizeof(noddy_NoddyObject), /
tp_basicsize/
0, /
tp_itemsize/
(destructor)Noddy_dealloc, /
tp_dealloc/
0, /
tp_print/
0, /
tp_getattr/
0, /
tp_setattr/
0, /
tp_compare/
0, /
tp_repr/
0, /
tp_as_number/
0, /
tp_as_sequence/
0, /
tp_as_mapping/
0, /
tp_hash /
0, /
tp_call/
0, /
tp_str/
0, /
tp_getattro/
0, /
tp_setattro/
0, /
tp_as_buffer/
Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HAVE_GC, /
tp_flags/
“Noddy objects”, /
tp_doc/
(traverseproc)Noddy_traverse, /
tp_traverse /
(inquiry)Noddy_clear, /
tp_clear /
0, /
tp_richcompare /
0, /
tp_weaklistoffset /
0, /
tp_iter /
0, /
tp_iternext /
Noddy_methods, /
tp_methods /
Noddy_members, /
tp_members /
0, /
tp_getset /
0, /
tp_base /
0, /
tp_dict /
0, /
tp_descr_get /
0, /
tp_descr_set /
0, /
tp_dictoffset /
(initproc)Noddy_init, /
tp_init /
0, /
tp_alloc /
Noddy_new, /
tp_new /
};

注意

Python的官方手册中说道如果设置了Py_TPFLAGS_HAVE_GC这个标志位的话,那么就必须使用PyObject_GC_New这个函数来创建实例对象,使用PyObject_GC_Del来销毁已创建了的实例对象。


使用PyObject_GC_New创建实例对象之后再用PyObject_GC_Track将该实例添加到垃圾回收器所跟踪的对象集合中去。

在对象销毁时再执行PyObject_GC_UnTrack和PyObject_GC_Del函数。


然后再修改tp_new函数和tp_dealloc函数:


static PyObject  Noddy_new(PyTypeObject type, PyObject args, PyObject kwds)
{
noddy_NoddyObject
self;
self = (noddy_NoddyObject)PyObject_GC_New(noddy_NoddyObject, type);
if (self != NULL) {
PyObject_GC_Track(self);
self->first = PyString_FromString(“”);
if (self->first == NULL)
{
Py_DECREF(self);
return NULL;
}
self->last = PyString_FromString(“”);
if (self->last == NULL)
{
Py_DECREF(self);
return NULL;
}
self->number = 0;
}
return (PyObject
)self;
}

static void Noddy_dealloc(noddy_NoddyObject* self)
{
PyObject_GC_UnTrack(self);
Noddy_clear(self);
PyObject_GC_Del(self);
}

最后再写一段Python程序来测试下该模块:


import gc
import noddy

gc.set_debug(gc.DEBUG_STATS | gc.DEBUG_LEAK)

o = noddy.Noddy()
l = [o]
o.first = l
del l
del o

gc.collect()

本文中的示例代码可从 https://github.com/wusuopu/python-c-extension-sample 获取到。

使用C语言编写Python扩展4——创建自定义类型(2)

上一节中我们创建了一个简单的类。这一节我们将对这个类进行扩展,添加属性、方法,并且支持子类。


为类型添加方法和数据


接着上一节的例子,继续编辑noddy.c


typedef struct {
PyObject_HEAD
/ Type-specific fields go here. /
PyObject first; / first name /
PyObject
last; / last name /
int number;
} noddy_NoddyObject;

修改 noddy_NoddyObject 结构体,为其添加三个字段。


然后定义自己的new方法,为对象分配内存空间:


static PyObject  Noddy_new(PyTypeObject type, PyObject args, PyObject kwds)
{
noddy_NoddyObjectself;
self = (noddy_NoddyObject
)type->tp_alloc(type, 0);
if (self != NULL) {
self->first = PyString_FromString(“”);
if (self->first == NULL)
{
Py_DECREF(self);
return NULL;
}
self->last = PyString_FromString(“”);
if (self->last == NULL)
{
Py_DECREF(self);
return NULL;
}
self->number = 0;
}
return (PyObject )self;
}

接着定义对象的初始化函数init


static int Noddy_init(noddy_NoddyObjectself, PyObject args, PyObject kwds)
{
PyObject first=NULL, last=NULL, tmp;
static char
kwlist[] = {“first”, “last”, “number”, NULL};

if (! PyArg_ParseTupleAndKeywords(args, kwds, “|OOi”, kwlist,
&first, &last,
&self->number))
return -1;

if (first) {
tmp = self->first;
Py_INCREF(first);
self->first = first;
Py_XDECREF(tmp);
}

if (last) {
tmp = self->last;
Py_INCREF(last);
self->last = last;
Py_XDECREF(tmp);
}
return 0;
}

由于对象包含了几项数据,因此在对象销毁时需要先释放数据的资源。定义资源释放方法:


static void Noddy_dealloc(noddy_NoddyObject self)
{
Py_XDECREF(self->first);
Py_XDECREF(self->last);
Py_TYPE(self)->tp_free((PyObject
)self);
}

然后再为该类定义一个方法用于返回该对象的first值和last值:


static PyObject  Noddy_name(noddy_NoddyObject self)
{
static PyObject format = NULL;
PyObject
args, result;
if (format == NULL) {
format = PyString_FromString(“%s %s”);
if (format == NULL)
return NULL;
}
if (self->first == NULL) {
PyErr_SetString(PyExc_AttributeError, “first”);
return NULL;
}
if (self->last == NULL) {
PyErr_SetString(PyExc_AttributeError, “last”);
return NULL;
}
args = Py_BuildValue(“OO”, self->first, self->last);
if (args == NULL)
return NULL;
result = PyString_Format(format, args);
Py_DECREF(args);
return result;
}
static PyMethodDef Noddy_methods[] = {
{“name”, (PyCFunction)Noddy_name, METH_NOARGS, “Return the name, combining the first and last name”},
{NULL} /
Sentinel /
};

最后在定义 noddy_NoddyType 变量时将对应字段进行填充:


static PyTypeObject noddy_NoddyType = {
PyObject_HEAD_INIT(NULL)
0, /
ob_size/
“noddy.Noddy”, /
tp_name/
sizeof(noddy_NoddyObject), /
tp_basicsize/
0, /
tp_itemsize/
(destructor)Noddy_dealloc, /
tp_dealloc/
0, /
tp_print/
0, /
tp_getattr/
0, /
tp_setattr/
0, /
tp_compare/
0, /
tp_repr/
0, /
tp_as_number/
0, /
tp_as_sequence/
0, /
tp_as_mapping/
0, /
tp_hash /
0, /
tp_call/
0, /
tp_str/
0, /
tp_getattro/
0, /
tp_setattro/
0, /
tp_as_buffer/
Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /
tp_flags/
“Noddy objects”, /
tp_doc/
0, /
tp_traverse /
0, /
tp_clear /
0, /
tp_richcompare /
0, /
tp_weaklistoffset /
0, /
tp_iter /
0, /
tp_iternext /
Noddy_methods, /
tp_methods /
0, /
tp_members /
0, /
tp_getset /
0, /
tp_base /
0, /
tp_dict /
0, /
tp_descr_get /
0, /
tp_descr_set /
0, /
tp_dictoffset /
(initproc)Noddy_init, /
tp_init /
0, /
tp_alloc /
Noddy_new, /
tp_new /
};

tp_flags字段增加了 Py_TPFLAGS_BASETYPE 属性,表示该类可以被继承。最后进行编译测试:


import noddy

o = noddy.Noddy(“abc”, “def”, 12)
print(o, o.name())
print(type(o), type(noddy.Noddy))
print(o.number, o.first, o.last)

class A(noddy.Noddy):
pass

运行以上程序会发现Noddy对象没有number、first和last这几个属性。这是因为虽然在noddy_NoddyObject结构体中定义了这几个字段,但是它们仍然在Python中是不可见的。

为了能在Python中访问这几个属性,需要设置noddy_NoddyType的tp_members字段。


static PyMemberDef Noddy_members[] = {
{“first”, T_OBJECT_EX, offsetof(noddy_NoddyObject, first), 0, “first name”},
{“last”, T_OBJECT_EX, offsetof(noddy_NoddyObject, last), 0, “last name”},
{“number”, T_INT, offsetof(noddy_NoddyObject, number), 0, “noddy number”},
{NULL} /
Sentinel /
};

先定义一个 PyMemberDef 结构体类型的数组,然后将noddy_NoddyType的tp_members字段设为Noddy_members。PyMemberDef和T_OBJECT_EX以及T_INT均是在 structmember.h 头文件中定义的,因此还需要先包含该文件。


#include <structmember.h>

数据属性的访问控制


数据属性的访问控制可以对属性的设置进行合法性检查,例如这里我们想要确保 Noddy 对象的first属性和last属性都必须是字符串。

首先定义属性的get方法和set方法:


static PyObject  Noddy_getfirst(noddy_NoddyObject self, void closure)
{
Py_INCREF(self->first);
return self->first;
}
static int Noddy_setfirst(noddy_NoddyObject self, PyObject value, void closure)
{
if (value == NULL) {
PyErr_SetString(PyExc_TypeError, “Cannot delete the first attribute”);
return -1;
}
if (! PyString_Check(value)) {
PyErr_SetString(PyExc_TypeError,
“The first attribute value must be a string”);
return -1;
}
Py_DECREF(self->first);
Py_INCREF(value);
self->first = value;
return 0;
}
static PyObject
Noddy_getlast(noddy_NoddyObject self, void closure)
{
Py_INCREF(self->last);
return self->last;
}
static int Noddy_setlast(noddy_NoddyObject self, PyObject value, void closure)
{
if (value == NULL) {
PyErr_SetString(PyExc_TypeError, “Cannot delete the last attribute”);
return -1;
}
if (! PyString_Check(value)) {
PyErr_SetString(PyExc_TypeError,
“The last attribute value must be a string”);
return -1;
}
Py_DECREF(self->last);
Py_INCREF(value);
self->last = value;
return 0;
}

然后创建一个 PyGetSetDef 结构类型的数组:


static PyGetSetDef Noddy_getseters[] = {
{“first”, (getter)Noddy_getfirst, (setter)Noddy_setfirst, “first name”, NULL},
{“last”, (getter)Noddy_getlast, (setter)Noddy_setlast, “last name”, NULL},
{NULL} /
Sentinel */
};

最后再设置 noddy_NoddyType 的tp_getset字段的值为 Noddy_getseters 即可。


注意:以上的代码均是针对Python2的,在Python3中略有不同。


本文中的示例代码可从 https://github.com/wusuopu/python-c-extension-sample 获取到。

使用C语言编写Python扩展3——创建自定义类型(1)

在Python代码中如果要创建一个自定义类使用class关键字即可,但是在C代码中就没那么方便了。

首先简单介绍下Python中的类型。在python中一切皆对象,python中有两种对象:

一种是类型对象(class对象):表示Python定义的类型,例如int, str, object等;

另一种是实例对象(instance对象):表示由class对象创建的实例。
Python中的所有对象都是直接或者间接继承object,然后object又是typy类型。可以运行下面的例子看看输出结果:

class A(object):
    pass

a = A()

print(type(a))
print(isinstance(a, A))
print(isinstance(a, object))
print(isinstance(a, type))

print(type(A))
print(A.__base__)
print(isinstance(A, object))
print(isinstance(A, type))

print(type(object))
print(isinstance(object, type))

print(type(type))
print(isinstance(type, object))

python是一门面向对象的编程语言,它是用C写的,而C又是面向过程的编程语言,那么python的类在C中是如何实现的呢?答案就是用结构体来模拟。

在Python的object.h头文件中定义了一个重要的结构体 PyTypeObject 。创建新的类型就是靠的它,该结构体定义如下:

typedef struct _typeobject {
    PyObject_VAR_HEAD
    char *tp_name; /* For printing, in format "<module>.<name>" */
    int tp_basicsize, tp_itemsize; /* For allocation */

    /* Methods to implement standard operations */

    destructor tp_dealloc;
    printfunc tp_print;
    getattrfunc tp_getattr;
    setattrfunc tp_setattr;
    cmpfunc tp_compare;
    reprfunc tp_repr;

    /* Method suites for standard classes */

    PyNumberMethods *tp_as_number;
    PySequenceMethods *tp_as_sequence;
    PyMappingMethods *tp_as_mapping;

    /* More standard operations (here for binary compatibility) */

    hashfunc tp_hash;
    ternaryfunc tp_call;
    reprfunc tp_str;
    getattrofunc tp_getattro;
    setattrofunc tp_setattro;

    /* Functions to access object as input/output buffer */
    PyBufferProcs *tp_as_buffer;

    /* Flags to define presence of optional/expanded features */
    long tp_flags;

    char *tp_doc; /* Documentation string */

    /* Assigned meaning in release 2.0 */
    /* call function for all accessible objects */
    traverseproc tp_traverse;

    /* delete references to contained objects */
    inquiry tp_clear;

    /* Assigned meaning in release 2.1 */
    /* rich comparisons */
    richcmpfunc tp_richcompare;

    /* weak reference enabler */
    long tp_weaklistoffset;

    /* Added in release 2.2 */
    /* Iterators */
    getiterfunc tp_iter;
    iternextfunc tp_iternext;

    /* Attribute descriptor and subclassing stuff */
    struct PyMethodDef *tp_methods;
    struct PyMemberDef *tp_members;
    struct PyGetSetDef *tp_getset;
    struct _typeobject *tp_base;
    PyObject *tp_dict;
    descrgetfunc tp_descr_get;
    descrsetfunc tp_descr_set;
    long tp_dictoffset;
    initproc tp_init;
    allocfunc tp_alloc;
    newfunc tp_new;
    freefunc tp_free; /* Low-level free-memory routine */
    inquiry tp_is_gc; /* For PyObject_IS_GC */
    PyObject *tp_bases;
    PyObject *tp_mro; /* method resolution order */
    PyObject *tp_cache;
    PyObject *tp_subclasses;
    PyObject *tp_weaklist;
} PyTypeObject;

这个比较庞大,里面包含的数据比较多,大部分都是一些函数指针而且可以为空,至于每个字段是什么意思请查看Python文档。

创建自定义类型

创建一个新的C代码文件 noddy.c ,然后我们编写一个名为 noddy 的扩展模块,该模块包含了一个名为 Noddy 的类。

首先创建一个新的 PyTypeObject 类型的变量:

typedef struct {
    PyObject_HEAD
    /* Type-specific fields go here. */
} noddy_NoddyObject;
static PyTypeObject noddy_NoddyType = {
    PyVarObject_HEAD_INIT(NULL, 0)
    "noddy.Noddy",             /*tp_name*/
    sizeof(noddy_NoddyObject), /*tp_basicsize*/
    0,                         /*tp_itemsize*/
    0,                         /*tp_dealloc*/
    0,                         /*tp_print*/
    0,                         /*tp_getattr*/
    0,                         /*tp_setattr*/
    0,                         /*tp_compare*/
    0,                         /*tp_repr*/
    0,                         /*tp_as_number*/
    0,                         /*tp_as_sequence*/
    0,                         /*tp_as_mapping*/
    0,                         /*tp_hash */
    0,                         /*tp_call*/
    0,                         /*tp_str*/
    0,                         /*tp_getattro*/
    0,                         /*tp_setattro*/
    0,                         /*tp_as_buffer*/
    Py_TPFLAGS_DEFAULT,        /*tp_flags*/
    "Noddy objects",           /*tp_doc*/
};

这里是定义了一个noddy_NoddyObject结构体,它的第一个字段为 PyObject_HEAD ,因此相当于一个PyObject类型;然后还有一个 noddy_NoddyType 变量,它的第一个字段为 PyVarObject_HEAD_INIT(NULL, 0) ,这个很很重要,按理说这个应该写成 PyVarObject_HEAD_INIT(&PyType_Type, 0) ,即表示Noddy这个类是一个type类型的对象。不过有的C编译器会对这个报错,因此这一项将在后面调用PyType_Ready函数来填充。
noddy_NoddyType 即是 Noddy 类,它保存了该类的元信息;noddy_NoddyObject结构体用于保存该类的实例对象的数据。
只要是定义的结构体以PyObject_HEAD开始就属于是一个PyObject类型。PyObject_VAR_HEAD与PyObject_HEAD相似,只是PyObject_HEAD表示的是该类型占用内存大小是固定的如int、float;而PyObject_VAR_HEAD表示该类型占用的内存是可变的如list、dict。

然后创建一个新扩展模块,并完成初始化:

static PyMethodDef noddy_methods[] = {
    {NULL}  /* Sentinel */
};
PyMODINIT_FUNC
initnoddy(void) 
{
    PyObject* m;

    noddy_NoddyType.tp_new = PyType_GenericNew;
    if (PyType_Ready(&noddy_NoddyType) < 0)
        return;

    m = Py_InitModule3("noddy", noddy_methods,
                       "Example module that creates an extension type.");

    Py_INCREF(&noddy_NoddyType);
    PyModule_AddObject(m, "Noddy", (PyObject *)&noddy_NoddyType);
}

注意:以上是针对Python2的,在Python3中模块的初始化操作略有不同。请参考第一节的内容。

noddy_NoddyType即是我们要创建的 Noddy 类,它是 PyTypeObject 类型的结构变量。为了创建新的类型,我们需要指明 tp_new 方法,它相当于Python中的 __new__,这里我们使用默认的 PyType_GenericNew 即可。
然后调用 PyType_Ready 完成新类型的创建。
最后调用 PyModule_AddObject 在该模块中添加刚刚创建的新类型。

测试

最后就是编写一个小程序来测试刚刚的模块是否可用。

import noddy

o = noddy.Noddy()
print(o)
print(type(o), type(noddy.Noddy))

# 这个会报错,noddy.Noddy 类不能被继承
class A(noddy.Noddy):
    pass

使用C语言编写Python扩展2——函数

上一节介绍了编写扩展的基本流程。这一回介绍一下在扩展模块中的函数调用,包括在扩展函数的参数提取和关键字参数解析,以及在C语言中调用Python方法。

同样的本文中的示例代码可从 https://github.com/wusuopu/python-c-extension-sample 获取到。

参数提取

接着上一节的例子,我们继续编辑lc_hello.c文件。先往模块中添加一个名为 func1 的函数,即就是在 lc_hello_world_methods 数组中添加一项:

{"func1", (PyCFunction)func1_function, METH_VARARGS, NULL},

然后就是对该函数的实现。
参数提取是使用 PyArg_ParseTuple 方法,其定义如下:

int PyArg_ParseTuple(PyObject *arg, char *format, ...);

其中 arg 参数为Python向C函数传递的参数列表,是一个无组对象;format 参数是一个格式化字符串,它的格式可以参考 Python/C API 文档。
func1_function 函数实现如下:

static PyObject* func1_function(PyObject *self, PyObject *args)
{
    int num, i, j;
    long lnum=0;
    const char* s1 = NULL;
    PyObject *obj = NULL;
    if (!PyArg_ParseTuple(args, "is(ii)|l",
                          &num, &s1, &i, &j, &lnum)) {
        printf("传入参数错误!\n");
        return NULL;
    }
    printf("num: %d\tstr1: %s\n"
           "i: %d\tj: %d\tlnum: %ld\n",
           num, s1, i, j, lnum);

    obj = Py_BuildValue("{sisisislss}",
                        "num", num, "i", i, "j", j, "lnum", lnum, "s1", s1);
    return obj;
}

在Python中该函数可以接收3个或者4个参数。同时该函数使用了 Py_BuildValue 方法构造了一个字典对象并返回。Py_BuildValue的用法与PyArg_ParseTuple类似。
接下来可以在Python中进行测试:

print(lc_hello_world.func1(11, 'abc', (2, 3), 100))
print(lc_hello_world.func1(11, 'abc', (2, 3)))

关键字参数

再在 lc_hello_world_methods 数组中添加一项:

{"func2", (PyCFunction)func2_function, METH_VARARGS | METH_KEYWORDS, NULL},

关键字参数解析是使用 PyArg_ParseTupleAndKeywords 方法,其定义如下:

int PyArg_ParseTupleAndKeywords(PyObject *arg, PyObject *kwdict,
                                char *format, char *kwlist[], ...);

其中 arg 参数和 format 参数与PyArg_ParseTuple一样。kwdict参数是一个字典对象,保存了关键字参数。kwlist是一个以NULL结尾的字符串数组。
func2_function 函数实现如下:

static PyObject* func2_function(PyObject *self, PyObject *args, PyObject *kwargs)
{
    int voltage;
    char *state = "a stiff";
    char *action = "voom";
    char *type = "Norwegian Blue";

    static char *kwlist[] = {"voltage", "state", "action", "type", NULL};

    if (!PyArg_ParseTupleAndKeywords(args, kwargs, "i|sss", kwlist,
                                     &voltage, &state, &action, &type))
        return NULL;

    printf("-- This parrot wouldn't %s if you put %i Volts through it.\n",
           action, voltage);
    printf("-- Lovely plumage, the %s -- It's %s!\n", type, state);
    Py_INCREF(Py_None);
    return Py_None;
}

接下来可以在Python中进行测试:

lc_hello_world.func2(state="ok", action="test", type="func", voltage=13)
lc_hello_world.func2(20)

在扩展模块中调用Python方法

在扩展模块中可以使用 PyObject_CallObject 方法来调用Python的函数方法。其定义如下:

PyObject* PyObject_CallObject(PyObject *callable_object, PyObject *args)

再在 lc_hello_world_methods 数组中添加一项:

{"func3", (PyCFunction)func3_function, METH_VARARGS, NULL},

func3_function 函数实现如下:

static PyObject* func3_function(PyObject *self, PyObject *args)
{
    PyObject *my_callback = NULL;
    PyObject *result = NULL;
    PyObject *arg = NULL;
    if (!PyArg_ParseTuple(args, "OO:set_callback;argument;", &my_callback, &arg)) {
        printf("传入参数错误!\n");
        return NULL;
    }
    if (!PyCallable_Check(my_callback)) {
        PyErr_SetString(PyExc_TypeError, "parameter must be callable");
        return NULL;
    }
    result = PyObject_CallObject(my_callback, arg);
    if (!result) {
        Py_INCREF(Py_None);
        result = Py_None;
    }
    return result;
}

接下来可以在Python中进行测试:

print(lc_hello_world.func3(int, (1.234, )))

使用C语言编写Python扩展1——Hello World

能够使用C语言编写扩展是Python一大卖点吧,这可以将一些关键的代码使用C来写以提升程序的性能。本文是参考了Python的官方文档整理而来的,同时结合了Python2跟Python3。按照惯例现在先从一个Hello World开始讲解一下写扩展的基本流程。

详细的内容可以参考官方文档:

https://docs.python.org/2.7/extending/index.html

https://docs.python.org/3/extending/index.html

https://docs.python.org/2.7/c-api/index.html

https://docs.python.org/3/c-api/index.html

同时本文中的示例代码可从 https://github.com/wusuopu/python-c-extension-sample 获取到。

首先介绍一下我当前的开发环境:

  • ArchLinux
  • gcc 4.8.2
  • glibc 2.19
  • Python 2.7.6
  • Python 3.3.5


    开始


    先创建一个新的C代码文件 lc_hello.c。为了能够正常使用python的api,需要导入Python.h这个头文件。


    #include <Python.h>

    然后再定义一个模块的初始化函数。


    PyMODINIT_FUNC initlc_hello_world(void)
    {
    Py_InitModule(“lc_hello_world”, lc_hello_world_methods);
    printf(“init lc_hello_world module\n”);
    }

    这个函数是用于模块初始化的,即是在第一次使用import语句导入模块时会执行。其函数名必须为initmodule_name这样的格式,在这里我们的模块名为lc_hello_world,所以函数名就是initlc_hello_world。

    在这个函数中又调用了Py_InitModule函数,它执行了模块初始化的操作。Py_InitModule函数传入了两个参数,第一个参数为字符串,表示模块的名称;第二个参数是一个PyMethodDef的结构体数组,表示该模块都具有哪些方法。与Py_InitModule相似的方法还有Py_InitModule3和Py_InitModule4。因此在initlc_hello_world方法之前还需要先定义 lc_hello_world_methods 数组。


    static PyMethodDef lc_hello_world_methods[] = {
    {“test”, (PyCFunction)test_function, METH_NOARGS, “lc_hello_world extending test”},
    {“add”, (PyCFunction)add_function, METH_VARARGS, NULL},
    {NULL, NULL, 0, NULL}
    };

    PyMethodDef结构体有四个字段。

    • 第一个是一个字符串,表示在Python中对应的方法的名称;
    • 第二个是对应的C代码的函数;
    • 第三个是一个标致位,表示该Python方法是否需要参数,METH_NOARGS表示不需要参数,METH_VARARGS表示需要参数;
    • 第四个是一个字符串,它是该方法的doc属性,这个不是必须的,可以为NULL。

      PyMethodDef结构体数组最后以 {NULL, NULL, 0, NULL}结尾。(感觉好像不是必须的,但是通常都这么做那我们也这么做吧)


      注意:以上的用法都是针对Python2的,在Python3中又有些不同。

      在Python3中模块的初始化函数的函数名变为了PyInit_module_name这样的形式了,因此这里就需要定义一个函数 PyMODINIT_FUNC PyInit_lc_hello_world。并且还需要返回一个 module 类型的变量。

      其次在Python3中创建module对象的函数也由 Py_InitModule 变为了 PyModule_Create。

      因此在Python3中模块的初始化函数应该定义如下:


      PyMODINIT_FUNC PyInit_lc_hello_world(void)
      {
      PyObject m;
      m = PyModule_Create(&lc_hello_world_module);
      if (m == NULL)
      return NULL;
      printf(“init lc_hello_world module\n”);
      return m;
      }

      PyModule_Create函数需要传入一个 PyModuleDef 类型的指针。

      因此在此之前还需要先定义 lc_hello_world_module 变量。


      static struct PyModuleDef lc_hello_world_module = {
      PyModuleDef_HEAD_INIT,
      “lc_hello_world”, /
      name of module /
      NULL, /
      module documentation, may be NULL /
      -1, /
      size of per-interpreter state of the module, or -1 if the module keeps state in global variables. /
      lc_hello_world_methods /
      A pointer to a table of module-level functions, described by PyMethodDef values. Can be NULL if no functions are present. /
      };

      在 lc_hello_world_methods 中我们为模块指定了两个方法,接下来我们需要实现这两个方法。


      static PyObject test_function(PyObject self)
      {
      PyObject_Print(self, stdout, 0);
      printf(“lc_hello_world test\n”);
      Py_INCREF(Py_True);
      return Py_True;
      }

      这段代码定义了Python的test方法所对应的C函数。在这个函数中就只执行了一条printf语句,然后就返回了Py_True。

      Py_True即是Python中的True,Py_INCREF函数执行的操作是对Python对象的计数引用值进行加1。与Py_INCREF对应的是Py_DECREF,它是对计数引用减1,并且计数引用为0时就销毁对象并回收内存。


      static PyObject add_function(PyObject self, PyObject args)
      {
      int num1, num2;
      PyObject result=NULL;
      if (!PyArg_ParseTuple(args, “nn”, &num1, &num2)) {
      printf(“传入参数错误!\n”);
      return NULL;
      }
      result = PyInt_FromLong(num1+num2);
      return result;
      }

      这须代码定义了Python的add方法所对应的C函数。该函数需要传入两个整数类型的参数。

      PyArg_ParseTuple是对传入的参数进行解析,关于这个函数的说明请查看Python手册。


      注意:在Python3中整数都是 long 类型的,因此这里的 PyInt_FromLong 需要改为 PyLong_FromLong,其作用是将C的int类型转为Python的int类型。


      编译


      扩展模块编写完成后,接下来就是对其进行编译了。先编写一个 setup.py 脚本。


      #!/usr/bin/env python
      #-
      - coding:utf-8 -*-

from setuptools import setup, Extension

hello_world = Extension(‘lc_hello_world’, sources=[“lc_hello.c”])
setup(ext_modules=[hello_world])

然后再执行命令进行编译:

$ python setup.py build

执行成功后会在当前目录下的build目录中生成扩展模块文件。

测试

最后就是编写一个小程序来测试刚刚的模块是否可用。

import lc_hello_world

print(lc_hello_world.test.__doc__)
print(lc_hello_world.add.__doc__)
print(lc_hello_world.test())
print(lc_hello_world.add(1, 2))
print(lc_hello_world.add(1, '2'))    # 这个会报错

mongoengine教程(5)——信号

MongoEngine在进行数据操作时会发出一些信号,我们可以连接这些信号进行一些额外的操作。注意:要在MongoEngine中使用信号,需要安装 blinker 这个库。


$ pip install blinker

MongoEngine提供的信号如下:



  • pre_init: 在创建一个新的 Document 或者 EmbeddedDocument 实例对象之后,并且对象初始化之前调用。

  • post_init:在 Document 或者 EmbeddedDocument 实例对象初始化完成之后调用。

  • pre_save:在 save 方法执行之前调用。

  • pre_save_post_validation:在数据检验完成之后,数据保存之前调用。

  • post_save:在数据保存完成之后调用。

  • pre_delete:在 delete 方法执行之前调用。

  • post_delete:在记录成功删除之后调用。

  • pre_bulk_insert:在数据检验之后,数据插入之前调用。

  • post_bulk_insert:在数据成功插入之后调用。


事件连接


使用 signals 将信号与回调函数进行连接。


from mongoengine import 
from mongoengine import signals

class Author(Document):
name = StringField()

@classmethod
def pre_save(cls, sender, document, *kwargs):
print(“Pre Save: %s” % document.name)

@classmethod
def post_save(cls, sender, document,
kwargs):
print(“Post Save: %s” % document.name)
if ‘created’ in kwargs:
if kwargs[‘created’]:
print(“Created”)
else:
print(“Updated”)

signals.pre_save.connect(Author.pre_save, sender=Author)
signals.post_save.connect(Author.post_save, sender=Author)

注意:对于 RefereneField 的reverse_delete_rules参数不会触发信号。

mongoengine教程(4)——文件存储

MongoDB的GridFS支持直接在数据库中存储文件。要在MongoEngine中使用GridFS,只要使用 FileField 对象即可。以下是一个例子:

class Animal(Document):
    genus = StringField()
    family = StringField()
    photo = FileField()

marmot = Animal(genus='Marmota', family='Sciuridae')

marmot_photo = open('gtk.png', 'rb')
marmot.photo.put(marmot_photo, content_type = 'image/png')
marmot.save()

这个例子将 gtk.png 这个图片存入了数据库中。
文件的读取也很简单:

marmot = Animal.objects(genus='Marmota').first()
photo = marmot.photo.read()
content_type = marmot.photo.content_type

FileField不仅可以存储文件,还可以用来存储数据流。只是操作上略微不同。

要存储数据流,首先先创建一个新的文件,然后再往里面写入数据。

marmot.photo.new_file()
marmot.photo.write('some_image_data')
marmot.photo.write('some_more_image_data')
marmot.photo.close()
marmot.save()

如果要删除存储在数据库中的文件,只需要调用该文件对象的 delete 方法:

marmot.photo.delete()

注意:一条文档记录中的FileField字段只保存了对GridFS集合中该文件的ID引用。这意味着如果该文档被删除了,对用的文件不会被删除。因此在删除这类文档时需要小心,以免出现孤立文件。

对于已存储的文件可以进行替换修改:

another_marmot = open('python.png', 'rb')
marmot.photo.replace(another_marmot, content_type='image/png')