<?xml version="1.0" encoding="utf-8" standalone="yes" ?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>Python on chai2010 的博客</title>
    <link>https://chai2010.cn/tags/python/</link>
    <description>Recent content in Python on chai2010 的博客</description>
    <generator>Hugo -- gohugo.io</generator>
    <language>zh-CN</language>
    <lastBuildDate>Mon, 01 Oct 2018 00:00:00 +0000</lastBuildDate>
    
        <atom:link href="https://chai2010.cn/tags/python/index.xml" rel="self" type="application/rss+xml" />
    
    
    <item>
      <title>Go实现Python模块</title>
      <link>https://chai2010.cn/post/2018/go-python/</link>
      <pubDate>Mon, 01 Oct 2018 00:00:00 +0000</pubDate>
      
      <guid>https://chai2010.cn/post/2018/go-python/</guid>
      
        <description>

&lt;p&gt;Go语言和Python语言作为当下最红的语言，使用Go语言来为Python语言编写模块是很多用户梦寐以求的方式。这里我们将演示如何通过Go语言来为Python脚本语言编写扩展模块。&lt;/p&gt;

&lt;p&gt;Python的本地模块采用C语言接口规范，因此Go语言编写的函数也需要以C语言接口规范导出。Go语言的CGO特性很早就实现了将Go语言编写为C语言的静态库或动态库。关于CGO的使用细节请参考作者编写的《Go语言高级编程》的第二章CGO编程部分。&lt;/p&gt;

&lt;h2 id=&#34;基于ctypes&#34;&gt;基于ctypes&lt;/h2&gt;

&lt;p&gt;Python内置了非常丰富的模块，其中ctypes支持直接从C动态库调用函数。为了演示如何基于ctypes技术来扩展模块，我们需要先用Go语言创建一个C动态库。&lt;/p&gt;

&lt;p&gt;我们使用的是之前出现过的例子：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-go&#34;&gt;// main.go

package main

import &amp;quot;C&amp;quot;
import &amp;quot;fmt&amp;quot;

func main() {}

//export SayHello
func SayHello(name *C.char) {
    fmt.Printf(&amp;quot;hello %s!\n&amp;quot;, C.GoString(name))
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;其中只导出了一个SayHello函数，用于打印字符串。通过以下命令基于上述Go代码创建say-hello.so动态库：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;go build -buildmode=c-shared -o say-hello.so main.go
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;现在我们就可以通过ctypes模块调用say-hello.so动态库中的SayHello函数了：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-python&#34;&gt;# hello.py
import ctypes

libso = ctypes.CDLL(&amp;quot;./say-hello.so&amp;quot;)

SayHello = libso.SayHello
SayHello.argtypes = [ctypes.c_char_p]
SayHello.restype = None

SayHello(ctypes.c_char_p(b&amp;quot;hello&amp;quot;))
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;我们首先通过ctypes.CDLL加载动态库到libso，并通过libso.SayHello来获取SayHello函数。获取到SayHello函数之后设置函数的输入参数为一个C语言类型的字符串，该函数没有返回值。然后我们通过&lt;code&gt;ctypes.c_char_p(b&amp;quot;hello&amp;quot;)&lt;/code&gt;将Python字节串转为C语言格式的字符串作为参数调用SayHello。如果一切正常的话就可以输出字符串了。&lt;/p&gt;

&lt;p&gt;从这个例子可以看出，给予ctypes构造Python扩展模块非常简单，本质上只是在构建一个纯C语言规格的动态库。比较复杂的部分在ctypes的具体使用，关于ctypes的具体细节就不详细展开的，用户可以自行参考Python自带的官方文档。&lt;/p&gt;

&lt;h2 id=&#34;基于python-c接口创建&#34;&gt;基于Python C接口创建&lt;/h2&gt;

&lt;p&gt;在前面的例子中，通过ctypes创建的模块必须要用Python再包装一层，否则就要直接面对C语言风格的接口。如果基于基于Python C接口，我们可以完全再Go和C语言层面创建灵活强大的模块，重点是不再需要在Python中重新包装。&lt;/p&gt;

&lt;p&gt;基于Python C接口创建模块和使用C语言的静态库的流程类似：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-go&#34;&gt;package main

/*
// macOS:
#cgo darwin pkg-config: python3

// linux
#cgo linux pkg-config: python3

// windows
// should generate libpython3.a from python3.lib

#define Py_LIMITED_API

#include &amp;lt;Python.h&amp;gt;

extern PyObject* PyInit_gopkg();
extern PyObject* Py_gopkg_sum(PyObject *, PyObject *);

static int cgo_PyArg_ParseTuple_ii(PyObject *arg, int *a, int *b) {
    return PyArg_ParseTuple(arg, &amp;quot;ii&amp;quot;, a, b);
}

static PyObject* cgo_PyInit_gopkg(void) {
    static PyMethodDef methods[] = {
        {&amp;quot;sum&amp;quot;, Py_gopkg_sum, METH_VARARGS, &amp;quot;Add two numbers.&amp;quot;},
        {NULL, NULL, 0, NULL},
    };
    static struct PyModuleDef module = {
        PyModuleDef_HEAD_INIT, &amp;quot;gopkg&amp;quot;, NULL, -1, methods,
    };

    return PyModule_Create(&amp;amp;module);
}
*/

import &amp;quot;C&amp;quot;

func main() {}

//export PyInit_gopkg
func PyInit_gopkg() *C.PyObject {
    return C.cgo_PyInit_gopkg()
}

//export Py_gopkg_sum
func Py_gopkg_sum(self, args *C.PyObject) *C.PyObject {
    var a, b C.int
    if C.cgo_PyArg_ParseTuple_ii(args, &amp;amp;a, &amp;amp;b) == 0 {
        return nil
    }

    return C.PyLong_FromLong(C.long(a + b))
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;因为Python的链接参数要复杂了很多，我们借助pkg-config工具来获取编译参数和链接参数。然后我们在Go语言中分别导出了PyInit_gopkg和Py_gopkg_sum函数，其中PyInit_gopkg函数用于初始化名为gopkg的Python模块，而Py_gopkg_sum函数则是模块中sum方法的实现。&lt;/p&gt;

&lt;p&gt;因此PyArg_ParseTuple是可变参数类型，CGO中无法使用可变参数的C函数，因此我们通过增加一个cgo_PyArg_ParseTuple_ii辅助函数小消除可变参数的影响。同样，模块的方法列表必须在C语言内存空间创建，因为CGO是禁止将Go语言内存直接返回到C语言空间的。&lt;/p&gt;

&lt;p&gt;然后通过以下命令创建gopkg.so动态库：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;go build -buildmode=c-shared -o gopkg.so main.go
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;这里需要注意几个出现gopkg名字的地方。gopkg是我们创建的Python模块的名字，因此它对应一个gopkg.so动态库。再gopkg.so动态库中必须有一个PyInit_gopkg函数，该函数是模块的初始化函数。在PyInit_gopkg函数初始化模块时，同样需要指定模块的名字时gopkg。模块中的方法函数是通过函数指针访问，具体的名字没有影响。&lt;/p&gt;

&lt;h3 id=&#34;macos环境构建&#34;&gt;macOS环境构建&lt;/h3&gt;

&lt;p&gt;因为在macOS中，pkg-config不支持Python3版本。不过macOS有一个python3-config的命令可以实现pkg-config类似的功能。不过python3-config生成的编译参数无法直接用于CGO编译选项（因为GCC不能识别部分参数会导致错误构建）。&lt;/p&gt;

&lt;p&gt;我们在python3-config的基础只是又包装了一个工具，在通过python3-config获取到编译参数之后将GCC不支持的参数剔除掉。&lt;/p&gt;

&lt;p&gt;创建py3-config.go文件：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-go&#34;&gt;func main() {
    for _, s := range os.Args {
        if s == &amp;quot;--cflags&amp;quot; {
            out, _ := exec.Command(
                &amp;quot;python3-config&amp;quot;, &amp;quot;--cflags&amp;quot;,
            ).CombinedOutput()

            out = bytes.Replace(out, []byte(&amp;quot;-arch&amp;quot;), []byte{}, -1)
            out = bytes.Replace(out, []byte(&amp;quot;i386&amp;quot;), []byte{}, -1)
            out = bytes.Replace(out, []byte(&amp;quot;x86_64&amp;quot;), []byte{}, -1)

            fmt.Print(string(out))
            return
        }
        if s == &amp;quot;--libs&amp;quot; {
            out, _ := exec.Command(
                &amp;quot;python3-config&amp;quot;, &amp;quot;--ldflags&amp;quot;,
            ).CombinedOutput()

            fmt.Print(string(out))
            return
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;cgo中的pkg-config只需要两个参数&lt;code&gt;--cflags&lt;/code&gt;和&lt;code&gt;--libs&lt;/code&gt;。其中&lt;code&gt;--libs&lt;/code&gt;选项的输出我们采用的是&lt;code&gt;python3-config --ldflags&lt;/code&gt;的输出，因为&lt;code&gt;--libs&lt;/code&gt;选项没有包含库的检索路径，而&lt;code&gt;--ldflags&lt;/code&gt;选项则是在指定链接库参数的基础上增加了库的检索路径。&lt;/p&gt;

&lt;p&gt;基于py3-config.go可以创建一个py3-config命令。然后通过PKG_CONFIG环境变量将cgo使用的pkg-config命令指定为我们订制的命令：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;PKG_CONFIG=./py3-config go build -buildmode=c-shared -o gopkg.so main.go
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;对于不支持pkg-config的平台我们都可以基于类似的方法处理。&lt;/p&gt;
</description>
      
    </item>
    
    <item>
      <title>PyQt&amp;OpenCV实现边缘检测</title>
      <link>https://chai2010.cn/post/2009/pyqt-opencv-edge/</link>
      <pubDate>Sun, 06 Dec 2009 00:00:00 +0000</pubDate>
      
      <guid>https://chai2010.cn/post/2009/pyqt-opencv-edge/</guid>
      
        <description>&lt;p&gt;首先设计UI界面, 调整自己满意的布局.
其中黑色为 QLabel, 对象名为labelImage, 用于显示图像. 左下角为拖动条, 对称为sliderThreshold, 用于修改参数.
Open按钮用于打开文件, 对象名为openButton.
在界面编辑器中, 将Close按钮的clicked信号连接到窗口的close槽.&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;https://chai2010.cn/images/pyqt-opencv-edge/pyqt-opencv-edge-01.jpg&#34; alt=&#34;&#34; /&gt;&lt;/p&gt;

&lt;p&gt;窗口设计完成后保存到edge.ui, 然后基于edge.ui文件构造程序:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;# -*- coding:utf-8 -*-
###########################################################
# PyQt+OpenCV example
#
# 查找图像中的边界.
# 参考 OpenCV\samples\python\edge.py 改写
#
# By chaishushan{AT}gmail.com 2008
###########################################################

import sys

# 导入PyQt模块
from PyQt4.Qt import *
from PyQt4 import uic

# 导入OpenCV模块
from opencv.cv import *
from opencv.highgui import *

# 边界检测类
class WinEdge(QWidget):
    def __init__(self, parent=None):
        QWidget.__init__(self, parent)
        uic.loadUi(&amp;quot;edge.ui&amp;quot;, self)

        # OpenCV相关参数
        self.cv_img = None
        self.cv_col_edge = None
        self.cv_gray = None
        self.cv_edge = None

        # 标题
        self.defaultTitle = self.windowTitle()


    @pyqtSignature(&amp;quot;&amp;quot;)
    def on_openButton_clicked(self):
        filename = QFileDialog.getOpenFileName(self,
                            self.tr(&amp;quot;Choose a Image&amp;quot;), &amp;quot;.&amp;quot;,
                            self.tr(&amp;quot;Image Files (*.jpg;*.bmp);;All Files (*)&amp;quot;))

        if not filename.isEmpty():
            self.sliderThreshold.setEnabled(False)

            self.openImage(filename)
            self.dectorEdge(self.sliderThreshold.value())

            # 更新UI
            if self.cv_col_edge:
                self.setWindowTitle(filename)
                self.sliderThreshold.setEnabled(True)
            else:
                self.setWindowTitle(self.defaultTitle)
                self.sliderThreshold.setEnabled(False)

    @pyqtSignature(&amp;quot;int&amp;quot;)
    def on_sliderThreshold_valueChanged(self, val):

        # 重新计算边界
        self.dectorEdge(val)

    def resizeEvent(self, event):
        self.showImage()

    # 打开图像
    def openImage(self, qstringName):
        # 将QString转换为char *
        filename = qstringName.toLocal8Bit().data()

        # 释放以前的图像
        if self.cv_img :
            cvReleaseImage( self.cv_img ); self.cv_img = None
            cvReleaseImage( self.cv_col_edge ); self.cv_col_edge = None
            cvReleaseImage( self.cv_gray ); self.cv_gray = None
            cvReleaseImage( self.cv_edge ); self.cv_edge = None

        # 打开新的图像
        self.cv_img = cvLoadImage(filename)
        if not self.cv_img:
            msg = self.tr(&amp;quot;Can&#39;t open %1 file!&amp;quot;).arg(filename)
            QMessageBox.warning (self, &amp;quot;Warning&amp;quot;, msg)
            return False

        # 创建辅助空间
        size = cvSize(self.cv_img.width, self.cv_img.height)

        self.cv_col_edge = cvCreateImage (size, 8, 3)
        self.cv_gray = cvCreateImage (size, 8, 1)
        self.cv_edge = cvCreateImage (size, 8, 1)

        return True

    # 生成边界

    def dectorEdge(self, position):

        if not self.cv_img: return

        # 转换为灰度
        cvCvtColor (self.cv_img, self.cv_gray, CV_BGR2GRAY)

        cvSmooth (self.cv_gray, self.cv_edge, CV_BLUR, 3, 3, 0)
        cvNot (self.cv_gray, self.cv_edge)

        # run the edge dector on gray scale
        cvCanny (self.cv_gray, self.cv_edge, position, position * 3, 3)

        # 结构保存到cv_col_edge中
        cvSetZero (self.cv_col_edge)
        cvCopy (self.cv_img, self.cv_col_edge, self.cv_edge)

        # 显示
        self.showImage()

    # 显示图像
    def showImage(self):
        if not self.cv_col_edge: return

        # 转化IplImage为QImage
        w = self.cv_col_edge.width
        h = self.cv_col_edge.height

        step = self.cv_col_edge.widthStep
        data = self.cv_col_edge.imageData

        img = QImage(data, w, h, step, QImage.Format_RGB888).rgbSwapped()

        # 显示图像[缩放到窗口大小]
        size = self.labelImage.size()
        self.labelImage.setPixmap(QPixmap.fromImage(img.scaled(size)))

if __name__ == &#39;__main__&#39;:
    app = QApplication(sys.argv)
    widget = WinEdge()
    widget.show()
    app.exec_()
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;运行结果如图:&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;https://chai2010.cn/images/pyqt-opencv-edge/pyqt-opencv-edge-02.jpg&#34; alt=&#34;&#34; /&gt;&lt;/p&gt;

&lt;p&gt;完整的代码以后补充。&lt;/p&gt;
</description>
      
    </item>
    
  </channel>
</rss>