新手学python(2):C语言调用完成数据库操作

来源:互联网 发布:域名历史记录查询 编辑:程序博客网 时间:2024/05/01 20:39

继续介绍本人的python学习过程。本节介绍如何利用python调用c代码。内容还是基于音乐信息提取的过程,架构如图一。Python调用c实现的功能是利用python访问c语言完成mysql数据库操作。


在利用python调用c语言之前,我们需要首先完成c语言功能代码,然后再考虑语言的转换问题,所以我们先介绍c语言实现的数据库访问代码。数据库操作主要包括DDL和DML,DDL在创建数据库和表时完成,c语言完成的是DML。在具体的实现中,c语言主要完成了:连接数据库,insert和select三个操作。可以认为音乐信息数据库是一个read-only数据库,只允许添加和检索,不允许删除(删除可以通过直接操作数据库完成)。

1. 数据库设计

       关于音乐信息的数据库只有一个表格:all_music,创建表的SQL语句如下:

create table all_music(    id integer auto_increment not null primary key,    original_name varchar(100),    name varchar(500) not null,    artist varchar(500),    genre varchar(30),    album varchar(500),    release_date date,    directory varchar(300),    size integer);

由于音乐没有很好的主键,所以我们采用surrogate key,并设定其为自动增长的字段。其他信息主要包括音乐名,歌手,专辑,类型和发行时间等。

2. C语言操作数据库

定义完数据库表格,我们就可以实现访问该表格的c代码,相关代码如下:

#include <mysql.h>typedef struct{    char original_name[100];    char name[500];    char artist[500];    char genre[30];    char album[500];    char date[20];    char directory[300];    int size;}music;typedef struct{    char id[20];    char original_name[100];    char directory[300];}row_result;char* column_name[8]={"original_name","name","artist","genre","album","release_date","directory","size"};MYSQL * connect_to(char* host,char* database,char* user,char* password){    MYSQL* con_ptr;    con_ptr=mysql_init(NULL);    if((con_ptr=mysql_real_connect(con_ptr,host,user,password,database,3306,NULL,0))==NULL)    {        fprintf(stderr,"connect failed:%s\n",mysql_error(con_ptr));        exit(-1);    }    return con_ptr;}void close_connect(MYSQL * con_ptr){    mysql_close(con_ptr);}void insert(MYSQL * con_ptr,char* table,music* m){    int res;    char sql[2000];    sprintf(sql,"insert into %s(%s,%s,%s,%s,%s,%s,%s,%s) values ('%s','%s','%s','%s','%s','%s','%s','%d')",table,column_name[0],column_name[1],column_name[2],column_name[3],column_name[4],column_name[5],column_name[6],column_name[7],m->original_name,m->name,m->artist,m->genre,m->album,m->date,m->directory,m->size);    res=mysql_query((MYSQL*)con_ptr,sql);    if(res)    {        fprintf(stderr,"insert failed:%s",mysql_error(con_ptr));        return;    }}MYSQL_RES * select_music(MYSQL * con_ptr,char* table){    int res;    char sql[100];    sprintf(sql,"select id,original_name,directory from %s ",table);    res=mysql_query((MYSQL*)con_ptr,sql);    if(res)    {        fprintf(stderr,"select failed:%s",mysql_error(con_ptr));        return NULL;    }    else    {        MYSQL_RES* result=mysql_store_result((MYSQL*)con_ptr);        if(result) return result;        else return NULL;    }}row_result * fetch_row(MYSQL_RES * result){    MYSQL_ROW mysql_row;    if(result==NULL)        return NULL;    mysql_row=mysql_fetch_row((MYSQL_RES*)result);    if(mysql_row)    {        row_result* row=(row_result*)malloc(sizeof(row_result));        strcpy(row->id,mysql_row[0]);        strcpy(row->original_name,mysql_row[1]);        strcpy(row->directory,mysql_row[2]);        return row;    }    else    {        fprintf(stderr,"no rows to return!\n");        return NULL;    }}void free_row(row_result * row){    free(row);}void free_result(MYSQL_RES * result){    mysql_free_result(result);}

上述代码首先定义了一个music结构体,对应要访问数据库表的一行,每一个元素的大小也和数据库表的定义一致。之后定义了一个表示返回结果的结构体row_result。此外,为了方便操作数据库表格,还定义了一个column_name数组表示数据库表的每一列。

       后面开始具体的数据库操作。connect_to函数建立与数据库的连接(特别注意不要将自定义的函数名字与库函数重名,否则会带来非常难找的bug!),并返回数据库连接指针。close_connect断开数据库连接。

Insert函数将一个music结构体对应的行插入数据库表中,代码的关键是构造一个没有错误的sql语句,构造sql语句时容易存在的问题是sql中如果存在“’”就会导致实际插入时的格式错误。这是因为当我们在指定某列的值时,需要采用类似'%s'这样的格式,如果要插入的数值也包括“’”就会导致错误的匹配。解决方案就是利用转义字符,python的mysql库为我们提供了一个可用的函数,在介绍python调用时会再次介绍。C语言貌似没有很好的函数解决该问题。

select_music函数会检索表中所有的行,我们利用mysql_store_result一次获得所有的行;另外一个可以利用的函数是mysql_use_result,这个函数会一次返回一行结果。两种函数的对比显而易见,但是在测试mysql_use_result时,它总会在返回部分结果后终止,不太可靠,因而采用了mysql_store_result函数。mysql_store_result返回的并不是可以直接访问的行数据,而是所有行的一个结果集,我们还需要利用fetch_row遍历结果集,获得每一行的真正数据。free_row和free_result分别释放每一行的空间和整个结果集。

最后,将上述代码打包成动态链接库,以供python调用。编译代码为:
gcc -I/usr/include/mysql  -g -pipe -Wp,-D_FORTIFY_SOURCE=2 -fexceptions -fstack-protector --param=ssp-buffer-size=4 -m64 -D_GNU_SOURCE -D_FILE_OFFSET_BITS=64 -D_LARGEFILE_SOURCE -fno-strict-aliasing -fwrapv db_operation.c -rdynamic -L/usr/lib64/mysql -lmysqlclient -lz -lcrypt -lnsl -lm -L/usr/lib64 -lssl -lcrypto -fPIC  -shared -o libdb_operation.so

3. Python调用C代码

3.1 数据类型的对应

在调用c代码时,我们需要创建music数据结构来存放插入的音乐信息,也需要row_result结构体来保存select返回的结果。如果利用python调用上面的c代码,我们不可避免地要创建上面两个结构体。为了将python中的数据结构映射到c中的数据结构,python提供了一个叫ctypes的包,用以实现数据类型的转换。ctypes是Python的一个外部库,提供和C语言兼容的数据类型,可以很方便地调用C DLL中的函数。针对上面定义的music结构体,python中对应的数据类型如下:

#!/usr/bin/python2.6from ctypes import *class Music(Structure):    _fields_=[            ('original_name',c_char*100),            ('name',c_char*500),            ('artist',c_char*500),            ('genre',c_char*30),            ('album',c_char*500),            ('release_date',c_char*20),            ('directory',c_char*300),            ('size',c_int)            ]    def setAttr(self,original_name,name,artist,genre,album,release_date,directory,size):        self.original_name=original_name;        self.name=name;        self.artist=artist;        self.genre=genre;        self.album=album;        self.release_date=release_date;        self.directory=directory;        self.size=size;

我们需要定义一个表示结构体的类Music,设置其_fields_属性,每一个属性的设置都包括属性名和属性类型。由于该类是在c中使用,所以数据类型都被转换为c语言可识别的类型,如c_char和c_int等。ctypes的类型对应如下:

和Music类似,表示返回结果的结构体在python中也有对应的类:

#!/usr/bin/python2.6from ctypes import *class Row(Structure):    _fields_=[            ('id',c_char*20),            ('original_name',c_char*100),            ('directory',c_char*300)            ]

3.2 函数调用

完成数据结构的对应之后,下面就可以实现具体的python函数:

#!/usr/bin/python2.6import sysimport ctypesimport music as mimport rowimport MySQLdb as mysqlclass CallDB:    connect_lib=ctypes.cdll.LoadLibrary('./libdb_operation.so');    connect_lib.fetch_row.restype=ctypes.POINTER(row.Row);#capitalized POINTER    @staticmethod    def connectTo(host,database,user,password):        c_host=ctypes.c_char_p(host);        c_database=ctypes.c_char_p(database);        c_user=ctypes.c_char_p(user);        c_password=ctypes.c_char_p(password);        c_con_ptr =CallDB.connect_lib.connect_to(c_host,c_database,c_user,c_password);        return c_con_ptr;    @staticmethod    def insert(c_con_ptr,table,music):        c_table=ctypes.c_char_p(table);        CallDB.connect_lib.insert(c_con_ptr,c_table,ctypes.pointer(music));    @staticmethod    def closeConnect(c_con_ptr):        CallDB.connect_lib.close_connect(c_con_ptr);    @staticmethod    def select(c_con_ptr,table):        c_table=ctypes.c_char_p(table);        c_result=CallDB.connect_lib.select_music(c_con_ptr,c_table);        return c_result;    @staticmethod    def fetchRow(c_result):        c_row_result=CallDB.connect_lib.fetch_row(c_result);        return c_row_result if c_row_result else None;    @staticmethod    def freeRow(c_row_result):        CallDB.connect_lib.free_row(c_row_result);    @staticmethod    def freeResult(c_result):        CallDB.connect_lib.free_result(c_result);

上述代码首先引入几个必要的包,然后定义一个类CallDB。类的开始定义了一个全局变量connect_lib表示加载的动态链接库。C语言实现的函数就是通过该全局变量进行访问。下一行代码稍后再做解释。

       第一个静态函数实现数据库的连接,调用的是c语言的connect_to函数。由于connect_to的参数都是c语言下的数据类型,我们不能直接传递python下的数据类型,需要首先利用ctypes将其转换成c语言可识别的类型。返回值c_con_ptr在c语言是一个MYSQL指针,python不知道其具体类型。由于我们在python中不会访问该指针,所以我们无需指定其具体类型。后面的静态函数通过调用c函数实现了数据库的插入和检索。

       可以看出,利用python实现基本的c调用很简单,但是需要注意两点。第一,非基本数据类型指针参数的传递。在insert函数中,music参数通过ctypes.pointer函数被转化成一个指针类型。虽然我们实现了c语言下music结构体在python下对应的类Music,但是python没有指针的概念,传递的参数必须被手动转换成指针。下面的代码演示了insert函数的具体使用:

music=m.Music();directory=mysql.escape_string(results[0]);album=mysql.escape_string(results[1].encode('utf-8'));release_date=results[2][4:8]+results[2][2:4]+results[2][0:2];name=mysql.escape_string(results[i][0].encode('utf-8'));genre=mysql.escape_string(results[i][1]);artist=mysql.escape_string(results[i][2].encode('utf-8'));original_name=mysql.escape_string(results[i][3]);size=int(results[i][4]);music.setAttr(original_name,name,artist,genre,album,release_date,directory,size);db.CallDB.insert(MyRequestHandler.c_con_ptr,'all_music',music);

在前面我们提到过SQL语句中转义字符的问题,MySQLdb为我们提供了一个函数escape_string可以解决转义的问题。第二,使用c代码的返回值。函数fetchRow在c语言下的返回值是一个row_result结构体指针。虽然这个指针在python下有对应的类Row,但是这需要我们手工指定,这就是开头代码

connect_lib.fetch_row.restype=ctypes.POINTER(row.Row);

的作用。特别注意,这个地方的POINTER需要大写,小写会报错。在获得返回值之后,访问对应的属性可通过下面的代码完成:

c_row_result=db.CallDB.fetchRow(MyRequestHandler.c_row_result);if c_row_result:     print c_row_result.contents.original_name;

如果是基本类型,如int,char则无需指定,可以直接访问返回值;如果返回类型是char*,则我们也需要手工指定返回类型为c_char_p。

       当然,如果单纯从数据库操作来看,完全可以利用MySQLdb包完成同样的功能,在此只是演示python如何调用c代码。

2 0
原创粉丝点击