新手学python(2):C语言调用完成数据库操作
继续介绍本人的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语言调用完成数据库操作的更多相关文章
- 使用C语言调用mysql数据库编程实战以及技巧
今天编写使用C语言调用mysql数据库编程实战以及技巧.为其它IT同行作为參考,当然有错误能够留言,共同学习. 一.mysql数据库的C语言经常使用接口API 1.首先当然是链接数据库mysql_re ...
- 小白学 Python(18):基础文件操作
人生苦短,我选Python 前文传送门 小白学 Python(1):开篇 小白学 Python(2):基础数据类型(上) 小白学 Python(3):基础数据类型(下) 小白学 Python(4):变 ...
- 小白学 Python(23):Excel 基础操作(上)
人生苦短,我选Python 前文传送门 小白学 Python(1):开篇 小白学 Python(2):基础数据类型(上) 小白学 Python(3):基础数据类型(下) 小白学 Python(4):变 ...
- 小白学 Python(24):Excel 基础操作(下)
人生苦短,我选Python 前文传送门 小白学 Python(1):开篇 小白学 Python(2):基础数据类型(上) 小白学 Python(3):基础数据类型(下) 小白学 Python(4):变 ...
- Python/MySQL(四、MySQL数据库操作)
Python/MySQL(四.MySQL数据库操作) 一.数据库条件语句: case when id>9 then ture else false 二.三元运算: if(isnull(xx)0, ...
- 【新手学Python】一、基础篇
由于以前处理数据用Matlab和C,最近要处理大量文本文件,用C写实在是太繁琐,鉴于Python的强大文本处理能力,以及其在Deep Learning上有着很大优势,本人打算从即日起学习Python, ...
- 小白学 Python(4):变量基础操作
人生苦短,我选Python 引言 前文传送门 小白学 Python(1):开篇 小白学 Python(2):基础数据类型(上) 小白学 Python(3):基础数据类型(下) 前面的文章中,我们介绍了 ...
- 新手学python(1):解析XML与系统调用
最近需要做一个项目,完成一批音乐的格式转换.由于之前并未学习过python,所以想借此机会学一下.在介绍自己的学习过程之前,先把项目简要描述一下.目前在一台服务器a上有几十万首原始的MP3音乐文件,现 ...
- 新手学python(3):yield与序列化
1 Yield生成器 Yield是我在其他语言中没有见过的一个属性,算是python的一大特色,用好之后可以使代码更简洁.考虑一个简单的例子,文件的遍历.要遍历一个目录下的所有文件需要递归的操作.如果 ...
随机推荐
- C语言程序设计第一次作业1
(一)实验总结 1. 圆面积问题 1:求圆面积和周长 (1)输入圆的半径,计算圆的周长和面积. (2) (3) 2:判断闰年 (1)输入一个四位年份,判断其是否是闰年.闰年的判别条件是该年年份能被4整 ...
- HashMap实现原理和源码解析
哈希表(hash table)也叫散列表,是一种非常重要的数据结构.许多缓存技术(比如memcached)的核心其实就是在内存中维护一张大的哈希表,本文会对java集合框架中的对应实现HashMap的 ...
- intellij配置hibernate自动生成hbm.xml文件
1.首先创建一个Java web项目,这里因为已经在整个项目中配置好tomcat了,所以我是直接创建module的,其实和创建project的配置方法一样,创建的时候选择Web Application ...
- SpringMVC之简单的增删改查示例(SSM整合)
本篇文章主要介绍了SpringMVC之简单的增删改查示例(SSM整合),这个例子是基于SpringMVC+Spring+Mybatis实现的.有兴趣的可以了解一下. 虽然已经在做关于SpringMVC ...
- Spring消息之AMQP.
一.AMQP 概述 AMQP(Advanced Message Queuing Protocol),高级消息队列协议. 简单回忆一下JMS的消息模型,可能会有助于理解AMQP的消息模型.在JMS中,有 ...
- Junit4 java.lang.Exception: No runnable methods
出现如下错误: java.lang.Exception: No runnable methods at org.junit.runners.BlockJUnit4ClassRunner.validat ...
- Azure AI 服务之语音识别
笔者在前文<Azure AI 服务之文本翻译>中简单介绍了 Azure 认知服务中的文本翻译 API,通过这些简单的 REST API 调用就可以轻松地进行机器翻译.如果能在程序中简单的集 ...
- Querying CRM data with LINQ
http://www.powerxrm.com/querying-crm-data-with-linq/ 如果不喜欢看SDK中的示例,这篇里面讲的非常详细,值得一看.
- hive指定hadoop执行队列
指定队列的命令: 有三种: set mapred.job.queue.name=queue3; SET mapreduce.job.queuename=queue3; set mapred.queue ...
- JAVA通过继承Thread来创建线程
创建一个线程的第二种方法是创建一个新的类,该类继承Thread类,然后创建一个该类的实例. 继承类必须重写run()方法,该方法是新线程的入口点.它也必须调用start()方法才能执行. 实例 // ...