继续介绍本人的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.6

from 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.6

from 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.6

import sys
import ctypes
import music as m
import row
import MySQLdb as mysql class 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代码。

新手学python(2):C语言调用完成数据库操作的更多相关文章

  1. 使用C语言调用mysql数据库编程实战以及技巧

    今天编写使用C语言调用mysql数据库编程实战以及技巧.为其它IT同行作为參考,当然有错误能够留言,共同学习. 一.mysql数据库的C语言经常使用接口API 1.首先当然是链接数据库mysql_re ...

  2. 小白学 Python(18):基础文件操作

    人生苦短,我选Python 前文传送门 小白学 Python(1):开篇 小白学 Python(2):基础数据类型(上) 小白学 Python(3):基础数据类型(下) 小白学 Python(4):变 ...

  3. 小白学 Python(23):Excel 基础操作(上)

    人生苦短,我选Python 前文传送门 小白学 Python(1):开篇 小白学 Python(2):基础数据类型(上) 小白学 Python(3):基础数据类型(下) 小白学 Python(4):变 ...

  4. 小白学 Python(24):Excel 基础操作(下)

    人生苦短,我选Python 前文传送门 小白学 Python(1):开篇 小白学 Python(2):基础数据类型(上) 小白学 Python(3):基础数据类型(下) 小白学 Python(4):变 ...

  5. Python/MySQL(四、MySQL数据库操作)

    Python/MySQL(四.MySQL数据库操作) 一.数据库条件语句: case when id>9 then ture else false 二.三元运算: if(isnull(xx)0, ...

  6. 【新手学Python】一、基础篇

    由于以前处理数据用Matlab和C,最近要处理大量文本文件,用C写实在是太繁琐,鉴于Python的强大文本处理能力,以及其在Deep Learning上有着很大优势,本人打算从即日起学习Python, ...

  7. 小白学 Python(4):变量基础操作

    人生苦短,我选Python 引言 前文传送门 小白学 Python(1):开篇 小白学 Python(2):基础数据类型(上) 小白学 Python(3):基础数据类型(下) 前面的文章中,我们介绍了 ...

  8. 新手学python(1):解析XML与系统调用

    最近需要做一个项目,完成一批音乐的格式转换.由于之前并未学习过python,所以想借此机会学一下.在介绍自己的学习过程之前,先把项目简要描述一下.目前在一台服务器a上有几十万首原始的MP3音乐文件,现 ...

  9. 新手学python(3):yield与序列化

    1 Yield生成器 Yield是我在其他语言中没有见过的一个属性,算是python的一大特色,用好之后可以使代码更简洁.考虑一个简单的例子,文件的遍历.要遍历一个目录下的所有文件需要递归的操作.如果 ...

随机推荐

  1. 《Java技术》第一次作业——Java语言基础

    学习总结 Scanner类实现基本数据输入的方法 Scanner 使用分隔符模式将其输入分解为标记,默认情况下该分隔符模式与空白匹配.然后可以使用不同的 next 方法将得到的标记转换为不同类型的值. ...

  2. swift之属性

    知识点总结: 1.存储属性 struct Town{ let region = "South" //只读属性 var population = //读写属性 } 2.惰性存储属性 ...

  3. jquery 引号问题

    varFrozenColumns="[[{'field':'CZ','title':'操作','width':80,'align':'center','formatter':function ...

  4. Maven的pom.xml文件结构之基本配置packaging和多模块聚合结构(微服务)

    1. packaging packaging给出了项目的打包类型,即作为项目的发布形式,其可能的类型.在Maven 3中,其可用的打包类型如下: jar,默认类型 war ejb ear rar pa ...

  5. RandomAccessFile读取文本简介

    RandomAccessFile类的常用的操作方法 1.public  RandomAccessFile(File file, String mode)throws FileNotFoundExcep ...

  6. Red Hat Enterprise Linux7的安装与oracle 12c的安装

    Red Hat Enterprise Linux7的安装与oracle 12c的安装 本文档中用到的所有参数均位于文末附录 Red Hat Enterprise Linux7的安装 新建完虚拟机后,挂 ...

  7. @RequestBody注解用法

    做Java已经有8个多月了,但是基本没有学习过Java语言,因此在项目中写代码基本靠的是其他语言的基础来写Java代码,写出来的很多代码虽然能用,但是感觉很不地道,虽然从来没有同事说过,但是我自己觉得 ...

  8. PHP Switch 语句

    PHP Switch 语句 switch 语句用于根据多个不同条件执行不同动作. PHP Switch 语句 如果您希望有选择地执行若干代码块之一,请使用 switch 语句. 语法 switch ( ...

  9. JMETER_从入门到放弃系列

    基础篇 Jmeter(一)_环境部署 Jmeter(二)_基础元件 Jmeter(三)_配置元件 Jmeter(四)_16个逻辑控制器 Jmeter(五)_24个函数 Jmeter(六)_前置处理器 ...

  10. Zookeeper的安装部署

    1.Zookeeper的安装部署 7.1 Zookeeper工作机制 7.1.1.Zookeeper集群角色 Zookeeper集群的角色:  Leader 和  follower (Observer ...