使用ruby开发vim插件

作为一个Vimmer和Pythoner,之前折腾过用python编写vim插件。现在作为半个Rubist,又开始继续折腾。


在开始编写插件之前,你需要确认 Vim 是否支持 Ruby,通过以下命令来判别:


$ vim –version | grep +ruby

如果输出为空,则表示你当前的vim不支持Ruby,需要重新编译一下,并启用对Ruby的支持。


顺便说下我当前的环境是:



  • vim 7.4

  • ruby 2.1.0


环境检查没有问题那么就开始吧。

在~/.vim/plugin目录下创建一个 demo.vim 文件。


在开头写上以下代码:


if !has(‘ruby’)
echo “Error: Required vim compiled with +ruby”
finish
endif

这段代码就是用 VimL 编写的,它将检查 Vim 是否支持 Ruby。


接下来再判断该插件是否已经加载过了,以免重复加载:


if exists(‘g:loaded_ruby_demo_plugin’)
finish
endif
let g:loaded_ruby_demo_plugin = 1

所有的检查都没有问题,则开始插件的正文了。先定义一个函数。


function! DemoFun1()
ruby<<EOF
buf = VIM::Buffer.current
puts “current buffer name: #{buf.name} number: #{buf.number} length: #{buf.length}”
EOF
endfunction

function与endfunction是vim中用于定义函数的,在”ruby<<EOF”和”EOF”之间部分的是Ruby代码。这个例子是输出当前缓冲区的名字、编号以及总行数。

执行命令:call DemoFun1(),应该就可以看到输出结果了。


然后再举个例子说下函数的参数处理。


function! DemoFun2(arg1)
ruby<< EOF
puts “you input: #{VIM.evaluate(‘a:arg1’)}”
EOF
endfunction

这里定义了一个函数接收一个参数,然后将其输出。使用 VIM.evaluate 将vim的变量转化为Ruby的变量。


为了方便我们再定义两个命令,以简化对这两个函数的调用。


command! -nargs=0 DemoFun1 call DemoFun1()
command! -nargs=1 -rang DemoFun2 call DemoFun2(<f-args>)

要获取完整的代码可以访问: https://gist.github.com/wusuopu/c1182efefa85d4f6839b


接下来再简单说下vim中Ruby的使用。


vim为Ruby提供了一个VIM模块,通过它可以在Ruby中访问vim的接口。同时还提供了两个全局变量:$curwin、$curbuf,它们分别代表了当前窗口对象以及当前缓冲区对象。


VIM模块中有 Buffer 和 Window 两个对象,分别是用来对缓冲区和窗口进行操作的。同时VIM模块还提供了message、set_option、command和evaluate四个函数。


想要查看更多的帮忙信息,可以在vim中执行如下命令:


:help ruby

好了,先写这么多吧,其余的自己去尝试吧。

vim的symfony2开发环境配置

安装

最近在用Symfony2框架进行开发,正所谓工欲善其事必先利其器。为了提高开发效率,因此将vim配置为symfony的IDE。


我安装的是phpcomplete-extended-symfony这款插件 https://github.com/m2mdas/phpcomplete-extended-symfony


它有些依赖在文档中没写清楚,我也是折腾了好久才搞定的,于是做个笔记记录一下。


我总共安装了以下插件:



  • neocomplete.vim

  • vimproc.vim

  • unite.vim

  • phpcomplete.vim

  • phpcomplete-extended

  • phpcomplete-extended-symfony

  • vim-twig


我是使用的Vundle进行vim插件管理,因此在 .vimrc 配置中添加以下内容:


Bundle ‘Shougo/neocomplete.vim’
Bundle ‘Shougo/vimproc.vim’
Bundle ‘Shougo/unite.vim’
Bundle ‘shawncplus/phpcomplete.vim’
Bundle ‘m2mdas/phpcomplete-extended’
Bundle ‘m2mdas/phpcomplete-extended-symfony’
Bundle ‘evidens/vim-twig’

然后再执行BundleInstall命令进行下载安装。


注意:vim-twig插件不是必需的,安装它只是为了编写twig模板时能够高亮。


使用


接下来说说使用流程。


1.vimproc需要编译

进行vimproc的安装目录,执行make命令进行编译。详细内容请阅读它的README文件。


2.安装ctags工具

由于当前的ctags工具不支持PHP的新特性,如命名空间、traits、interface,于是就需要给ctags打补丁。


下载打过补丁的ctags源代码:


$ wget “https://github.com/shawncplus/phpcomplete.vim/blob/master/misc/ctags-better-php-parser.tar.bz2?raw=true“ -O ctags-better-php-parser.tar.bz2

编译安装:


$ tar xvjf ctags-better-php-parser.tar.bz2
$ ./configure
$ make
$ sudo make install

3.安装 composer.phar


$ curl -s https://getcomposer.org/installer | php

4.修改vim配置

在vim中添加如下配置:


au FileType php setlocal omnifunc=phpcomplete_extended#CompletePHP
let g:phpcomplete_index_composer_command = ‘composer.phar’

5.开始使用

进入Symfony项目的根目录,执行命令生成tags文件:


$ ctags -R –fields=+aimS –languages=php

然后使用vim打开该tags文件,根据提示创建索引文件。完成之后索引文件会创建在 .phpcomplete_extended 目录中。下次在项目的根目录下打开vim,如果提示 “Index Loaded.”则表示配置成功了。接下来编写代码时就会有补全提示以及use语句的自动补全了。


注意:在使用该插件时需要把自动切换目录功能给禁用掉,添加设置: set autochdir! 。否则会出现无法加载索引,从而补全不了。这个问题在文档中没说,我也是折腾了好久才发现的。

Redis使用笔记

Redis是一个键值型数据库,之前花了些时间体验了一下Redis,感觉还是很不错的。而且Redis学习起来也很容易,差不多花几个小时应该就能入门了。当时我是看的《the little redis book》,初学者可以看看。

安装

下载软件包: http://redis.io/download

首先运行服务器程序:

$ redis-server

程序默认是监听6379端口。

然后再运行客户端程序:

$ redis-cli

基本数据类型

运行客户端程序连接上Redis服务之后,所有的操作都可以通过该命令行交互完成。

Redis有5种不同的数据类型:字符串、散列表、列表、集合、有序集合。所有的数据在Redis中都是以键值对的形式保存的。

以下介绍这几种类型数据的基本操作。

String

设置一个键的值为字符串:

set <key> <value>

获取该键的值:

get <key>

字符串相关的命令:

help @string

Hashes

设置一个散列值:

hset <key> <k> <v>

获取该键的值:

hget <key> <k>  
hgetall <key>

散列相关的命令:

help @hash

Lists

添加值:

lpush <key> <v>

移除值:

lpop <key>  
help @list

Set

添加值:

sadd <key> <v>[ <v> ..]  
help set

Sorted Sets

类似于集合(Set),但是提供了排序(sorting)和秩划分(ranking)的功能。

添加值:

zadd key score member [score] [member]   
help @sorted_set

其他

有效期

设置某个值在seconds秒后到期:

expire <key> seconds

设置某个值在time时刻到期:

expireat <key> time

查看到期时间:

ttl <key>

清除到期时间:

persist <key>

事务(Transactions)

multi  
do something  
exec

multi和exec之间的命令作为原子操作。

在multi之前调用watch可监听值的变化,若该值被其他客户端修改,事务将会运行失败。

杂项

选择数据库:

select index

清除当前数据库所属key:

flushdb

清除所有数据库所属key:

flushall

查看key所储存的值的类型:

type key

Redis相关的命令手册: http://redis.io/commands

在应用程序中使用Redis

Redis提供了多种语言的绑定,以Ruby为例:

require 'redis'
r = Redis.new
r.set 'key', 1234
puts r.get 'key'
puts r.keys

输出结果应该为:

"1234"
["key"]

使用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, )))