Archive for the ‘Python’ category

Python 调用 C++ DLL

January 28th, 2018

update 2018-2-4

早些时候用Pyqt写了一个app,最近需要加入一个License的认证模块,然而这个认证模块是拿C++写的,并只给出了dll、lib和header。。。网上查找了一些资料,一种方法是采用ctypes 去加载dll;另外一种就是编写一个PyObject 然后编译成pyd模块供python调用。

这里我采用第一种方式,他们提供给我的模块是_stdll的方式,因为然后我又把这个dll包了一层,以__cdecl方式导出。

#ifdef LICENSEDLL_EXPORTS
#define LICENSEDLL_API extern "C" __declspec(dllexport)
#else
#define LICENSEDLL_API __declspec(dllimport)
#endif

LICENSEDLL_API bool  verify(char *module, char *errorMsg);
.....

然后我将这个工程编译生成LicenseDLL.dll。下面就是python方面的任务了,我引入ctypes,然后利用CDLL加载LicenseDLL.dll,利用dll.verify.argtypes,我声明verify函数具有两个参数,其中pinfo为100个字节的数组,并将其传入verify函数。该函数返回后,写入ErrorInfo,使用QString(str(ErrorInfo.raw))将其转换成QString对象,完成对于dll的调用。

def CheckLicenseInfo():
    from ctypes import *
    dll = CDLL(app.g_pwd + os.sep + 'LicenseDLL.dll')
    dll.verify.argtypes = [c_char_p,c_char_p]
    pinfo = c_char * 100
    ErrorInfo = pinfo()

    if not dll.verify('DESIGNAPP', ErrorInfo):
        QMessageBox.critical(None, 'Error', QString(str(ErrorInfo.raw)))
        exit()

但是这种方式有个最大的问题:由于直接读dll,可能会存在dll依赖链的问题,比如我操作的时候就遇到Windows Error 126和127的问题,126应该就是dll依赖缺失,需要使用depandency的问题,127有可能是dll本身的问题,没有把里面的函数符号导出。127比较复杂,有时候遇到挺懵逼的….. 

编写python扩展也是一种可行的方式,可以编译成pyd的形式,在python下直接import。因为python要调用C++的模块,那么必然涉及到python对象向C++传值和传地址的问题,也涉及到C++返回值向python传值的问题,直接上源码来一步一步说明吧。

编写代码首先要引入Python.h Python.lib 默认用安装包安装的是没有python_d.lib的,当然也要引入需要wapper的头文件,我根据官方提供的spamError例子代码修改为我的代码,总的框架代码有PyMODINIT_FUNC initXXXXX(void),static PyMethodDef LicenseMethods[]={…},还有常规的warpper代码static PyObject *
xxxxxxxxxx(PyObject *self, PyObject *args)。PyMODINIT_FUNC initXXXXX(void)为固定格式,把XXXXX换成我们编写的模块即可。

#include "Python.h"
#include "LicenseProve.h"

static PyObject *LicenseError;

static PyObject *isHavingLicense(PyObject *self, PyObject *args)
{
    const char *name;
	char *err;
    if (!PyArg_ParseTuple(args, "s|s", &name,&err))
        return NULL;

    std::string modelName(name);
    std::string errorMsg(err);

    bool ret = Allone::License::CLicenseProve::getInstance()->isHavingLicense(modelName,errorMsg);

	//if(ret == false)
	//	return Py_False;
    //return Py_True;
    return Py_BuildValue("(iss)",(int)ret,modelName.c_str(),errorMsg.c_str());
}

static PyObject *getLicenseType(PyObject *self, PyObject *args)
{
    size_t ret = Allone::License::CLicenseProve::getInstance()->getLicenseType();
    return PyInt_FromSize_t(ret);
}

static PyObject *getLicenseInfo(PyObject *self, PyObject *args){...}
static PyObject *getRegisterInfo(PyObject *self, PyObject *args){...}
static PyObject *getAppendInfo(PyObject *self, PyObject *args){...}

static PyMethodDef LicenseMethods[] = {
    {"isHavingLicense",  isHavingLicense, METH_VARARGS,"Having License in this Computer?"},
	{"getLicenseType", getLicenseType, METH_NOARGS,"Get License type"},
	{"getLicenseInfo", getLicenseInfo, METH_NOARGS,"Get License Info"},
	{"getRegisterInfo", getRegisterInfo, METH_NOARGS,"Get License Register Info"},
	{"getAppendInfo", getAppendInfo, METH_NOARGS,"Get License Append Info"},
    {NULL, NULL, 0, NULL}        /* Sentinel */
};

PyMODINIT_FUNC initLicense(void)
{
    PyObject *m;

    m = Py_InitModule("License", LicenseMethods);
    if (m == NULL)
        return;

    LicenseError = PyErr_NewException("License.error", NULL, NULL);
    Py_INCREF(LicenseError);
    PyModule_AddObject(m, "error", LicenseError);
}

在LicenseMethods需要加入我们编写的函数,双引号里面的是python调用的函数名,第二个参数是c++函数里面的函数名,第三个是声明传入参数类型,METH_VARARGS为可变参数,METH_NOARGS是没有参数。这个实质上就是一个参数表,连接python和c++端的实现。c++端的实现都写成static PyObject *
xxxxxxxxxx(PyObject *self, PyObject *args),我们需要的参数都是通过args以Tuple传入,调用PyArg_ParseTuple(args, “s|s”, &name,&err),可以拿到传入的值,”s|s”表示元祖里每个参数的类型,常用类型可以在官方wiki中找到,s就是代表string,i就是代表integer等等。后面就是根据指定的类型,传入到name和error中,C++中可以使用参数传值,但是python的参数传入的都是值,因此我将修改的的值都放置到一个tuple中返回给python,也就是使用Py_BuildValue返回,参数和PyArg_ParseTuple类似,第一个是每个参数的类型,后面是参数值。如果返回值简单,我们也可以直接返回Py_False等。

在PyMODINIT_FUNC initLicense(void)中,大致也分为Py_InitModule初始化,PyErr_NewException注册,在异常处理这一块,我没有细看,但是这个应该是处理各种C++发生的异常,由于python不用管理内存,但是在C++中我们就必须处理,因此Py_INCREF用来管理引用计数,最后还需要将error和注册的python模块连接起来。

在python中,只需要使用import License,直接引入即可,看下面代码:

def CheckLicenseInfo2():
    error = ""
    # print(License.isHavingLicense('DESIGNAPP',error))
    ret = License.isHavingLicense('DESIGNAPP',error)
    err = ret[2]
    if not ret[0]:
        QMessageBox.critical(None, 'Error', QString(str(err)))
        exit()

    print(License.getLicenseType())
    print(License.getLicenseInfo())
    print(License.getRegisterInfo())
    print(License.getAppendInfo())

由于isHavingLicense返回的是tuple,因此我们只要指明索引,就可以找到需要的内容。更多的参数类型可以查看C:\Python27\include,下面都是各种的传入传出函数和宏,满足我们的需要。

这种pyd的方式实质上也是dll,但是比直接调用ctype读取dll有更好的兼容性,在实际生产中推荐使用pyd调用C++函数函数。

 

参考:

http://www.cnblogs.com/night-ride-depart/p/4907613.html

https://docs.python.org/2/library/ctypes.html

http://wolfprojects.altervista.org/dllforpyinc.php

http://icejoywoo.github.io/2015/10/30/intro-of-extending-cpython.html

https://docs.python.org/2/c-api/arg.html

https://docs.python.org/2/extending/extending.html?highlight=meth_varargs

http://blog.csdn.net/mkc1989/article/details/38943927

Python 包管理工具总结

December 4th, 2017

最近一直使用elasticsearch-py 操作数据库,最开始我是clone的他的官方仓库,然后使用python setup.py install方式安装的,虽然也可以使用,调用他的包没有什么问题,但是在pycharm中一直出现红色下划线,当遇到参数错误的时候,也没有办法跳入接口,看内部实现,比较抓狂。。。。。

考虑到之前配置python包遇到很多小问题,这次一次性把技术债还了。

通过查资料,大致可以理出来distutils、setuptools、distribute、disutils2、distlib、pip这几个工具的出现先后:

  1. 首先出现的安装工具是distutils,distutils 是 python 标准库的一部分,我们在python工程中的setup.py就是利用distutils完成的,他的工作原理很简单,但是功能有限。
  2. 为了完善distutils工具,产生了setuptools,它包含了 easy_install 这个工具;其中 ez_setup.py 是 setuptools 的安装工具,ez 就是 easy 的缩写。我们可以是使用
    easy_install http://example.com/Package-1.2.3.tgz  .egg 方式安装。
  3. distribute 是 setuptools 的一个分支版本,目前distribute 又合并回了 setuptools 中。本质上是同一个东西。如果查看一下 easy_install 的版本,它本质上就是 distribute 。
  4. distutils2是一个新的distutils库,作为distutils代码库的一个分支。
  5. distlib是distutils2的一部分
  6. pip是目前 python 包管理的事实标准,2008年发布。用来替换 easy_install,但是它仍有大量的功能建立在 setuptools 组件之上。

以上工具中distutils、setuptools、distribute、pip是主流包管理器,disutils2、distlib还需要观察。

eggs Vs whl 

Eggs 格式是 setuptools 引入的一种文件格式,它使用 .egg 扩展名,用于 Python 模块的安装。而setuptools 可以识别这种格式。并解析安装它。

wheel 本质上是一个 zip 包格式,它使用 .whl 扩展名,用于 python 模块的安装,它的出现是为了替代 Eggs。

eggs和whl本质上都是压缩包,我们都可以通过修改后缀名,解压提取内容!但是在pip中不太推荐egg的安装方式,因为egg安装后,只是把这个egg安装包放到dist-package中,而whl本质是一种源码安装,安装后在dist-package中存在源码和同名的info文件来描述这个安装包,因此我们可以在调用的时候,查看接口,而egg在编译器看来就是一堆二进制数据。

拿elasticsearch-py源码包举例,从github上clone最新的源码到本地,我们可以使用python setup.py install 直接将egg安装到/usr/local/lib/python2.7/dist-package中,而源码放在elasticsearch-py源码中的build中,所以可以使用python setup.py sdist 将其压缩为egg,然后使用pip安装该egg。另外也可以将源码打成rpm : python setup.py bdist_rpm   exe: python setup.py bdist_wininst

但是还是推荐打成whl格式的安装包,python setup.py bdist_wheel 。这个会将源码安装到dist-package中。

依赖安装:setup.py和requirements.txt的对比 这篇文章主要参数了两种安装文件的异同,归纳起来就是setup.py无法灵活限定软件版本,而requirements.txt可以限定具体软件包的版本,可以配合setup.py实现。

requirements.txt:

--index https://pypi.python.org/simple/
-e https://github.com/foo/bar.git#egg=bar
-e .

比如 pip install -r requirements.txt 可以照常工作,它会先安装requirements路径下的bar包,然后继续开始解析抽象依赖,结合 –index 选项后转换为具体依赖然后再安装她们。

这个办法可以让我们解决一种类似这样的情形:比如有两个或两个以上的包在一起开发但是是分开发行的,或者说有一个尚未发布的包并把它分成了几个部分。如果顶层的包 依然仅仅按照“名字”来依赖的话,我们依然可以使用requirements.txt 来安装开发版本的依赖包。

 

参考

https://docs.python.org/3/distutils/introduction.html?highlight=distutils#a-simple-example

http://blog.csdn.net/lynn_kong/article/details/17540207

https://stackoverflow.com/questions/6344076/differences-between-distribute-distutils-setuptools-and-distutils2/14753678#14753678

numpy,Pandas 使用小结

August 31st, 2017

Update 2017-9-20

最近被一些生活上的事搞得焦头烂额,博客又有些荒废了,继续更…..

网络上有大段的numpy和Pandas博文,我就不一一阐述了,只记录我最近使用出现的坑….

numpy和Pandas配合matplotlib使用,可以快速绘制各类图表,我们可以方便的使用pandas读取xlsx,csv格式的数据.例如我们读取xlsx格式的数据,我们可以在read_excel定义表格是否存在header,便于跳过。pandas读取返回数据类型是dataFrame,而dataframe的每一行数据又是一个ndarray类型,在知道数据表头的前提下,可以将data看成一个大的数组,data[‘列名’]打印某列数据。data每列的列名就是一个key,列数据就是一个value,通过这种方式可以对某几列进行计算。

    pd.set_option('display.width', 200)
    data = pd.read_excel('sales.xlsx', sheetname='sheet1', header=0)
    print 'data.head() = \n', data.head()
    print 'data.tail() = \n', data.tail()
    print 'data.dtypes = \n', data.dtypes
    print 'data.columns = \n', data.columns
    for c in data.columns:
        print c,
    print
    data['total'] = data['Jan'] + data['Feb'] + data['Mar']
    print data.head()
    print data['Jan'].sum()
    print data['Jan'].min()
    print data['Jan'].max()
    print data['Jan'].mean()

reindex是对这个dataframe重新组织列名,rename则是对某一行的头进行重命名。

pd.reindex(columns=data.columns)
data = data.rename(index={15:'Total'})

除了read_excel外,read_csv可以帮助我们处理cvs格式的数据,当我们接手一个csv数据文件后,要观察这个文件是否存在表头,存在话,可以在data = pd.read_csv(‘xxx’,header=None)跳过,data的数据类型是dataFrame,如果我们想获取某一列数据或者某几列,可以采用iloc[],(这里采用iloc返回的是Seriers类型,如果使用values则转换为np。array类型,而array类型通过tolist则可以转换为 python 内置的list类型)

data = pd.read_csv('wine_data',sep=',',header=None)
x = data.iloc[:,1:].values
y = data.iloc[:,0].vaues

当然也可以生成一个表头,并且在pd.read_csv(‘wine_data’,sep=’,’,header=cols)制定表头是cols,而cols是一个np.array,当读入数据以后,就可以使用表头获取某几列数据x = data[cols[:-1]]

df.loc[1] 获取第二行

df.loc[:,’test1′] 获取test1的那一列,这个冒号的意思是所有行,逗号表示行与列的区分

df.loc[:,[‘test1′,’test2’]] 获取test1列和test2列的数据

df.loc[1,[‘test1′,’test2’]] 获取第二行的test1和test2列的数据

df.at[1,’test1′] 表示取第二行,test1列的数据,和上面的方法类似

df.iloc[0] 获取第一行

df.iloc[0:2,0:2] 获取前两行前两列的数据

df.iloc[[1,2,4],[0,2]] 获取第1,2,4行中的0,2列的数据

apply函数是对某一行或者某一列使用定义的函数进行处理,例如函数find_state_code,在python,numpy和pandas中axis是一个比较难以理解的概念,stackoverflow给出了非常好的解释翻译过来就是axis=0沿着列的方向,axis=1沿着行的方向。下面代码就是沿着行的方向把每个行中列名为state的元素进行修改!

def find_state_code(row):
    if row['state'] != 0:
        print process.extractOne(row['state'], states, score_cutoff=80)

data.apply(find_state_code, axis=1)

pandas.cut将值放入某个bin中,并将bin赋值给这列数据。

>>> pd.cut(np.array([.2, 1.4, 2.5, 6.2, 9.7, 2.1]), 3,labels=["good","medium","bad"])
[good, good, good, medium, bad, good]
Categories (3, object): [good < medium < bad]

numpy中提供np.linalg.svd函数,对图像进行奇异值分解,然后我们可以将得到的特征值和特征向量相乘复原这张图像,例如一张图高282px 宽218px rgb,那么分解出来就是u 是282×282 v是218×218 sigma 218.那么u[:, k]代表第k列reshape成为mx1形状的矩阵,v[k]代表k行reshape成为1xn形状的矩阵,二者通过np.dot(uk, vk)做矩阵点乘,再乘sigma即可还原图像。

a = np.array(A)

u_r, sigma_r, v_r = np.linalg.svd(a[:, :, 0])
u_g, sigma_g, v_g = np.linalg.svd(a[:, :, 1])
u_b, sigma_b, v_b = np.linalg.svd(a[:, :, 2])
=====
def restore1(sigma, u, v, K):  # 奇异值、左特征向量、右特征向量
    m = len(u)
    n = len(v[0])
    a = np.zeros((m, n))
    for k in range(K):
        uk = u[:, k].reshape(m, 1)
        vk = v[k].reshape(1, n)
        a += sigma[k] * np.dot(uk, vk)
    a[a &lt; 0] = 0 a[a &gt; 255] = 255
    # a = a.clip(0, 255)
    return np.rint(a).astype('uint8')

np.loadtxt可以读入格式化数据,然后通过usecols选取某几个特定行。np.convolve是特定针对一维向量做卷积的函数,这个函数开始我很疑惑,因为他和对于图像做卷积很不同,通过查询文档(https://docs.scipy.org/doc/numpy-1.10.0/reference/generated/numpy.convolve.html),了解到第二个参数是卷积核,python内部实现的时候对这个卷积核做了一个反转,然后才对数据做卷积。

stock_max, stock_min, stock_close, stock_amount = np.loadtxt('SH600000.txt', delimiter='\t', skiprows=2, usecols=(2, 3, 4, 5), unpack=True)
stock_sma = np.convolve(stock_close, weight, mode='valid')  # simple moving average
============
np.convolve([1, 2, 3], [0, 1, 0.5])
array([ 0. ,  1. ,  2.5,  4. ,  1.5])

具体计算方法是将[0, 1, 0.5]翻转为[0.5,1,0],对[1,2,3]做卷积,[0,0,1],[0,1,2],[1,2,3],[2,3,0],[3,0,0]分别点乘[0.5,1,0]

0.5*null+1*null+0*1=0
0.5*null+1*1+0*0*2=1
0.5*1+1*2+0*3=2.5
0.5*2+1*3+0*null=4
0.5*3+1*null+0*null=1.5

np.polyfit曲线拟合函数,第三个参数是多项式最高次数,拟合完毕结果返回10次拟合多项式系数, 从高次到低次存放在向量poly中.polyval可求得多项式在t处的值stock_ema_hat,这里的stock_ema,stock_ema_hat 都是向量。

poly = np.polyfit(t, stock_ema, 10)
stock_ema_hat = np.polyval(poly, t)

candlestick_ohlc是专门用于画股市K线图的,这个包来自于from matplotlib.finance import candlestick_ohlc

one-hot编码主要解决许多不连续的值,比如【男,女】【小学,初中,高中,本科,其他】,对于这种数据使用N位状态寄存器来对N个状态进行编码,每个状态都由他独立的寄存器位,并且在任意时候,其中只有一位有效。

自然状态码为:000,001,010,011,100,101

one-hot编码为:000001,000010,000100,001000,010000,100000

可以看出one-hot编码每个编码互斥,每次只有一个激活。数据变成稀疏的。在pandas中直接使用get_dummies()对这些离散值进行one-hot编码,如下代码所示,data[col]特定指某一列离散值。

data = data.join(pd.get_dummies(data[col],prefix=col))
print data

此外sklearn提供LabelEncoder,可以一次性将所有离散值进行编码(非one-hot编码)。

le = LabelEncoder()
for col in data.columns:
    data[col] = le.fit_transform(data[col])

MinMaxScaler,说白了就是归一化,计算方式是特征值减去最小值除以最大值减去最小值。

mms = MinMaxScaler()
data[col] = mms.fit_transform(data[col].values.reshape(-1,1))

pca主成分分析主要用于降维,但是本质上是坐标系的旋转,比如我们使用pca将多维数据降维成2维数据,降维后的坐标轴,其实没有什么实际的意义,但是pca可以让数据更加可视化,便于理解。
采用单变量特征选择(Univariate feature selection)则是在众多变量中选择最重要的变量,抛弃不重要的变量。

pca = PCA(n_components=2,whiten=True,random_state=0)
x = pca.fit_transform(x)
print pca.explained_variance_
print pca.explained_variane_ratio_

fs = SelectKBest(chi2,k=2)
fs.fit(x,y)
idx = fs.get_support(indices=True)//idx 返回选中主要的两列(k=2)

numpy下存在numpy.ravel() 和 numpy.flatten(),两者的功能是一致的(将多维数组降位一维),numpy.flatten()返回一份拷贝,对拷贝所做的修改不会影响(reflects)原始矩阵,而numpy.ravel()返回的是视图(view,也颇有几分C/C++引用reference的意味),会影响(reflects)原始矩阵。

================

附引入包

from sklearn.preprocessing import LabelEncoder
from sklearn.preprocessing import MinMaxScaler
from sklearn.decomposition import PCA
from sklearn.feature_selection import SelectKBest, SelectPercentile, chi2
from sklearn.linear_model import LogisticRegressionCV
from sklearn import metrics
from sklearn.model_selection import train_test_split
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import PolynomialFeatures
from sklearn.manifold import TSNE
from matplotlib.finance import candlestick_ohlc
from PIL import Image

 

参考

https://docs.scipy.org/doc/numpy-1.10.0/reference/generated/numpy.convolve.html

http://pandas.pydata.org/pandas-docs/stable/generated/pandas.cut.html

http://blog.csdn.net/ariessurfer/article/details/42526673

http://scikit-learn.org/stable/modules/preprocessing.html#preprocessing

ndarrya用法汇总: http://blog.csdn.net/qingyuanluofeng/article/details/51649789

http://pandas.pydata.org/pandas-docs/stable/generated/pandas.read_csv.html

http://skyrover.me/2016/12/08/Pandas%E4%BD%BF%E7%94%A8%E7%AC%94%E8%AE%B0/

Python 快速学习教程

January 7th, 2015

» Read more: Python 快速学习教程