Python 扩展技术总结
2014-12-09 16:05
141 查看
一般来说,所有能被整合或导入到其他Python脚本中的代码,都可以称为扩展。你可以用纯Python来写扩展,也可以用C/C++之类的编译型语言来写扩展,甚至可以用java,C都可以来写
python扩展。Python的一大特点是,扩展和解释器之间的交互方式域普通的Python模块完全一样,Python的模块导入机制非常抽象,抽象到让使用模块的代码无法了解到模块的具体实现细节。
Python进行扩展的主要原因有三点:(1)添加额外的Python语言的核心部分没有提供的功能 (2)为了提升性能瓶颈的效率(3)保持专有源代码私密,把一部分代码从Python转到编译语言可以保持专有源代码私密
方法一:利用C API进行C扩展
这种方法是最基本的,包括三个步骤:1.创建应用程序代码 2.利用样板来包装代码 3.编译
1.创建一个Extest.c文件 包含两个C函数
Extest.c
使用样板分为4步:
1.添加Python的头文件
2.为每一个模块的每个函数增加一个形如PyObject* Module_func()的包装函数
3.为每一个模块增加一个形如PyMethodDef ModuleMethods[]的数组
4.增加模块初始化函数 void initModule()
第二步的处理需要一些技巧,你需要为所有想Python环境访问的函数增加一个静态函数。函数的返回值类型为 PyObject*,在Python的C语言扩展接口中,大部分函数都有一个或者多个参数为PyObject指针类型,并且返回值也大都为PyObject指针.
包装函数的用处是先把Python的值传递给C,然后调用C函数把函数的计算结果转换成Python 对象,然后返回给Python
第三步每一个数组都包含一个 函数信息,包括函数Python的名字,相应的包装函数的名字以及一个METH_VARARGS常量(表示参数以元组的形式传入)
Extest.c
在编译阶段需要创建一个setup.py,通过setup.py来编译和链接代码。这一步完成后就就可以直接导入这个扩展的模块了.在setup.py中需要导入distutils包。首先你要为每一个扩展创建一个Extension实例,然后编译的操作主要由setup()函数来完成,它需要两个参数:一个名字参数表示要编译哪个东西,一个列表表示要编译的对象
setup.py
执行的结果 是在当前目录中生成一个build 目录,在此目录下有一个Extest.so文件,然后就可以在脚本中import这个模块了
方法二:利用Ctypes进行C扩展
为了扩展Python,我们可以用C/C++编写模块,但是这要求对Python的底层有足够的了解,包括Python对象模
型、常用模块、引用计数等,门槛较高,且不方便利用现有的C库。而ctypes
则另辟蹊径,通过封装
接加以利用。
一个简单的实例:
这个例子直接利用ctypes使用C标准库函数而不用编写C代码,使用C标准库函数会产生优化效果
脚本一
第一个脚本使用 Python的随机函数,运行时间约为1.067秒 第二个脚本使用C的随机函数 运行时间约为0.423秒
我们也利用Ctypes可以自己写模块导入使用:
步骤一创建应用程序代码
执行指令:gcc
-Wall -fPIC -c functions.c
gcc -shared -o libfunctions.so functions.o
步骤三:在自己写的python脚本中使用这些库,下面的脚本分别用纯python的模块(输出时前面加了python),和我们导入的扩展(输出时前面加了C)模块来运行,对比其模块执行时间,
functions.py
functions2.py
运行结果
可以看出纯python模块的运行时间是我们写的扩展模块运行时间的十倍以上
方法三:利用Cython
进行C扩展 (参考Cython三分钟入门)
Cyhton 是一个用来快速生成 Python 扩展模块(extention module)的工具,它的语法是 python语言语法和 C 语言语法的混血。准确说 Cython 是单独的一门语言,专门用来写在 Python 里面import 用的扩展库。实际上 Cython 的语法基本上跟 Python 一致,而Cython 有专门的“编译器”;先将 Cython 代码转变成 C(自动加入了一大堆的 C-Python API),然后使用
C 编译器编译出最终的 Python可调用的模块。
要注意的一点是, Cython 是用来生成 C 扩展到而不是独立的程序的。所有的加速都是针对一个已经存在的 Python 应用的一个函数进行的。没有使用 C 或 Lisp 重写整个应用程序,也没有手写 C 扩展 。只是用一个简单的方法来整合 C 的速度和 C 数据类型到 Python函数中去
Cython 代码跟 Python 不一样,必须要编译。 编译经过两个阶段:(1) Cython 编译.pyx 文件为.c 文件 (2) C 编译器会把.c 文件编译成.so 文件.生成.so 文件后表示重写函数成功,可以在 python 代码中直接调用这个模块
Python代码
#p1.py
import math
def great_circle(lon1,lat1,lon2,lat2):
radius = 3956 #miles
x = math.pi/180.0
a = (90.0-lat1)*(x)
b = (90.0-lat2)*(x)
theta = (lon2-lon1)*(x)
c = math.acos((math.cos(a)*math.cos(b)) +
(math.sin(a)*math.sin(b)*math.cos(theta)))
return radius*c
Python代码
#p1_test.py
import timeit
lon1, lat1, lon2, lat2 = -72.345, 34.323, -61.823, 54.826
num = 500000
t = timeit.Timer("p1.great_circle(%f,%f,%f,%f)" % (lon1,lat1,lon2,lat2),
"import p1")
print "Pure python function", t.timeit(num), "sec"
Python代码
# 测试结果
Pure python function 2.25580382347 sec
#c1.pyx
import math
def great_circle(float lon1,float lat1,float lon2,float lat2):
cdef float radius = 3956.0
cdef float pi = 3.14159265
cdef float x = pi/180.0
cdef float a,b,theta,c
a = (90.0-lat1)*(x)
b = (90.0-lat2)*(x)
theta = (lon2-lon1)*(x)
c = math.acos((math.cos(a)*math.cos(b)) + (math.sin(a)*math.sin(b)*math.cos(theta)))
return radius*c
Python代码
# setup.py
from distutils.core import setup
from distutils.extension import Extension
from Cython.Distutils import build_ext
ext_modules=[
Extension("c1",
["c1.pyx"])
]
setup(
name = "Demos",
cmdclass = {"build_ext": build_ext},
ext_modules = ext_modules
)
python setup.py build_ext --inplace
Python代码
#c1_test.py
import timeit
lon1, lat1, lon2, lat2 = -72.345, 34.323, -61.823, 54.826
num = 500000
t = timeit.Timer("c1.great_circle(%f,%f,%f,%f)" % (lon1,lat1,lon2,lat2),
"import c1")
print "Pure python function", t.timeit(num), "sec"
Python代码
#执行结果:
Pure python function 1.87078690529 sec
#c2.pyx
cdef extern from "math.h":
float cosf(float theta)
float sinf(float theta)
float acosf(float theta)
def great_circle(float lon1,float lat1,float lon2,float lat2):
cdef float radius = 3956.0
cdef float pi = 3.14159265
cdef float x = pi/180.0
cdef float a,b,theta,c
a = (90.0-lat1)*(x)
b = (90.0-lat2)*(x)
theta = (lon2-lon1)*(x)
c = acosf((cosf(a)*cosf(b)) + (sinf(a)*sinf(b)*cosf(theta)))
return radius*c
Python代码
#setup.py
from distutils.core import setup
from distutils.extension import Extension
from Cython.Distutils import build_ext
ext_modules=[
Extension("c2",
["c2.pyx"],
libraries=["m"]) # Unix-like specific
]
setup(
name = "Demos",
cmdclass = {"build_ext": build_ext},
ext_modules = ext_modules
)
python setup.py build_ext --inplace
Python代码
# c2_test.py
import timeit
lon1, lat1, lon2, lat2 = -72.345, 34.323, -61.823, 54.826
num = 500000
t = timeit.Timer("c2.great_circle(%f,%f,%f,%f)" % (lon1,lat1,lon2,lat2),
"import c2")
print "Pure python function", t.timeit(num), "sec"
Python代码
#执行结果
Pure python function 0.34069108963 sec
#c3.pyx
cdef extern from "math.h":
float cosf(float theta)
float sinf(float theta)
float acosf(float theta)
cdef float _great_circle(float lon1,float lat1,float lon2,float lat2):
cdef float radius = 3956.0
cdef float pi = 3.14159265
cdef float x = pi/180.0
cdef float a,b,theta,c
a = (90.0-lat1)*(x)
b = (90.0-lat2)*(x)
theta = (lon2-lon1)*(x)
c = acosf((cosf(a)*cosf(b)) + (sinf(a)*sinf(b)*cosf(theta)))
return radius*c
def great_circle(float lon1,float lat1,float lon2,float lat2,int num):
cdef int i
cdef float x
for i from 0 <= i < num:
x = _great_circle(lon1,lat1,lon2,lat2)
return x
C-sharp代码
#setup.py
from distutils.core import setup
from distutils.extension import Extension
from Cython.Distutils import build_ext
ext_modules=[
Extension("c3",
["c3.pyx"],
libraries=["m"]) # Unix-like specific
]
setup(
name = "Demos",
cmdclass = {"build_ext": build_ext},
ext_modules = ext_modules
)
python setup.py build_ext --inplace
Python代码
#c3_test.py
import timeit
lon1, lat1, lon2, lat2 = -72.345, 34.323, -61.823, 54.826
num = 500000
t = timeit.Timer("c2.great_circle(%f,%f,%f,%f)" % (lon1,lat1,lon2,lat2),
"import c2")
print "Pure python function", t.timeit(num), "sec"
Python代码
#测试结果
Pure python function 0.340164899826 sec
# python代码
Pure python function 2.25580382347 sec
# Cython,使用Python的math模块
Pure python function 1.87078690529 sec
# Cython,使用C的math库
Pure python function 0.34069108963 sec
# Cython,使用纯粹的C函数
Pure python function 0.340164899826 sec
python扩展。Python的一大特点是,扩展和解释器之间的交互方式域普通的Python模块完全一样,Python的模块导入机制非常抽象,抽象到让使用模块的代码无法了解到模块的具体实现细节。
Python进行扩展的主要原因有三点:(1)添加额外的Python语言的核心部分没有提供的功能 (2)为了提升性能瓶颈的效率(3)保持专有源代码私密,把一部分代码从Python转到编译语言可以保持专有源代码私密
方法一:利用C API进行C扩展
这种方法是最基本的,包括三个步骤:1.创建应用程序代码 2.利用样板来包装代码 3.编译
1.创建一个Extest.c文件 包含两个C函数
Extest.c
#include <stdio.h> #include <stdlib.h> #include <string.h> #define BUFSIZE 10 int fac(int n) { if (n < 2) return 1; return n * fac(n - 1); } //字符串反转 char *reverse(char *s) { register char t; char *p = s; char *q = (s + (strlen(s) - 1)); while (p < q) { t = *p; *p++ = *q; *q-- = t; } return s; } <strong><span style="font-size:18px;"> </span></strong>2.用样板来包装你的代码
使用样板分为4步:
1.添加Python的头文件
2.为每一个模块的每个函数增加一个形如PyObject* Module_func()的包装函数
3.为每一个模块增加一个形如PyMethodDef ModuleMethods[]的数组
4.增加模块初始化函数 void initModule()
第二步的处理需要一些技巧,你需要为所有想Python环境访问的函数增加一个静态函数。函数的返回值类型为 PyObject*,在Python的C语言扩展接口中,大部分函数都有一个或者多个参数为PyObject指针类型,并且返回值也大都为PyObject指针.
包装函数的用处是先把Python的值传递给C,然后调用C函数把函数的计算结果转换成Python 对象,然后返回给Python
第三步每一个数组都包含一个 函数信息,包括函数Python的名字,相应的包装函数的名字以及一个METH_VARARGS常量(表示参数以元组的形式传入)
Extest.c
#include <stdio.h> #include <stdlib.h> #include <string.h> #include "Python.h" //包含python头文件 #define BUFSIZE 10 int fac(int n) { if (n < 2) return 1; return n * fac(n - 1); } char *reverse(char *s) { register char t; char *p = s; char *q = (s + (strlen(s) - 1)); while (p < q) { t = *p; *p++ = *q; *q-- = t; } return s; } //fac函数的包装函数 static PyObject * Extest_fac(PyObject *self, PyObject *args) { int num; if (!(PyArg_ParseTuple(args, "i", &num))) { return NULL; } return (PyObject *)Py_BuildValue("i", fac(num)); } //reverse函数的包装函数 static PyObject * Extest_doppel(PyObject *self, PyObject *args) { char *orignal; char *reversed; PyObject * retval; if (!(PyArg_ParseTuple(args, "s", &orignal))) { return NULL; } retval = (PyObject *)Py_BuildValue("ss", orignal, reversed=reverse(strdup(orignal))); free(reversed); return retval; } //为模块创建一个函数信息的数组 static PyMethodDef ExtestMethods[] = { {"fac", Extest_fac, METH_VARARGS}, {"doppel", Extest_doppel, METH_VARARGS}, }; //增加模块初始化函数 void initExtest() { Py_InitModule("Extest", ExtestMethods); }
在编译阶段需要创建一个setup.py,通过setup.py来编译和链接代码。这一步完成后就就可以直接导入这个扩展的模块了.在setup.py中需要导入distutils包。首先你要为每一个扩展创建一个Extension实例,然后编译的操作主要由setup()函数来完成,它需要两个参数:一个名字参数表示要编译哪个东西,一个列表表示要编译的对象
setup.py
<pre name="code" class="python">from distutils.core import setup, Extension MOD = 'Extest' setup(name=MOD, ext_modules=[ Extension(MOD, sources=['Extest.c'])])运行setup.py :执行命令 python setup.py build
执行的结果 是在当前目录中生成一个build 目录,在此目录下有一个Extest.so文件,然后就可以在脚本中import这个模块了
方法二:利用Ctypes进行C扩展
为了扩展Python,我们可以用C/C++编写模块,但是这要求对Python的底层有足够的了解,包括Python对象模
型、常用模块、引用计数等,门槛较高,且不方便利用现有的C库。而ctypes
则另辟蹊径,通过封装
dlopen/dlsym之类的函数,并提供对C中数据结构的包装/解包,让Python能够加载动态库、导出其中的函数直
接加以利用。
一个简单的实例:
这个例子直接利用ctypes使用C标准库函数而不用编写C代码,使用C标准库函数会产生优化效果
脚本一
import timeit import random def generate(num): while num: yield random.randrange(10) num -= 1 print(timeit.timeit("sum(generate(999))", setup="from __main__ import generate", number=1000))
<span style="font-size:18px;color:#FF0000;">脚本二</span> import timeit from ctypes import cdll def generate_c(num): #Load standard C library libc = cdll.LoadLibrary("libc.so.6") #Linux # libc = cdll.msvcrt #Windows while num: yield libc.rand() % 10 num -= 1 print(timeit.timeit("sum(generate_c(999))", setup="from __main__ import generate_c", number=1000))
第一个脚本使用 Python的随机函数,运行时间约为1.067秒 第二个脚本使用C的随机函数 运行时间约为0.423秒
我们也利用Ctypes可以自己写模块导入使用:
步骤一创建应用程序代码
/* functions.c */ #include "stdio.h" #include "stdlib.h" #include "string.h" /* http://rosettacode.org/wiki/Sorting_algorithms/Merge_sort#C */ inline void merge(int *left, int l_len, int *right, int r_len, int *out) { int i, j, k; for (i = j = k = 0; i < l_len && j < r_len; ) out[k++] = left[i] < right[j] ? left[i++] : right[j++]; while (i < l_len) out[k++] = left[i++]; while (j < r_len) out[k++] = right[j++]; } /* inner recursion of merge sort */ void recur(int *buf, int *tmp, int len) { int l = len / 2; if (len <= 1) return; /* note that buf and tmp are swapped */ recur(tmp, buf, l); recur(tmp + l, buf + l, len - l); merge(tmp, l, tmp + l, len - l, buf); } /* preparation work before recursion */ void merge_sort(int *buf, int len) { /* call alloc, copy and free only once */ int *tmp = malloc(sizeof(int) * len); memcpy(tmp, buf, sizeof(int) * len); recur(buf, tmp, len); free(tmp); } int fibRec(int n){ if(n < 2) return n; else return fibRec(n-1) + fibRec(n-2); } ~步骤二:将它编译链接为.so文件
执行指令:gcc
-Wall -fPIC -c functions.c
gcc -shared -o libfunctions.so functions.o
步骤三:在自己写的python脚本中使用这些库,下面的脚本分别用纯python的模块(输出时前面加了python),和我们导入的扩展(输出时前面加了C)模块来运行,对比其模块执行时间,
functions.py
from ctypes import * import time libfunctions = cdll.LoadLibrary("./libfunctions.so") def fibRec(n): if n<2 : return n else: return fibRec(n-1) + fibRec(n-2) start = time.time() fibRec(32) finish = time.time() print("Python: " + str(finish - start)) #C Fibonacci start = time.time() x = libfunctions.fibRec(32) finish = time.time() print("C: " + str(finish - start))
functions2.py
from ctypes import * import time libfunctions = cdll.LoadLibrary("./libfunctions.so") #Python Merge Sort from random import shuffle, sample #Generate 9999 random numbers between 0 and 100000 numbers = sample(range(100000), 9999) shuffle(numbers) c_numbers = (c_int * len(numbers))(*numbers) from heapq import merge def merge_sort(m): if len(m) <= 1: return m middle = len(m) // 2 left = m[:middle] right = m[middle:] left = merge_sort(left) right = merge_sort(right) return list(merge(left, right)) start = time.time() numbers = merge_sort(numbers) finish = time.time() print("Python: " + str(finish - start)) #C Merge Sort start = time.time() libfunctions.merge_sort(byref(c_numbers), len(numbers)) finish = time.time() print("C: " + str(finish - start)) ~ ~ ~
运行结果
可以看出纯python模块的运行时间是我们写的扩展模块运行时间的十倍以上
方法三:利用Cython
进行C扩展 (参考Cython三分钟入门)
Cyhton 是一个用来快速生成 Python 扩展模块(extention module)的工具,它的语法是 python语言语法和 C 语言语法的混血。准确说 Cython 是单独的一门语言,专门用来写在 Python 里面import 用的扩展库。实际上 Cython 的语法基本上跟 Python 一致,而Cython 有专门的“编译器”;先将 Cython 代码转变成 C(自动加入了一大堆的 C-Python API),然后使用
C 编译器编译出最终的 Python可调用的模块。
要注意的一点是, Cython 是用来生成 C 扩展到而不是独立的程序的。所有的加速都是针对一个已经存在的 Python 应用的一个函数进行的。没有使用 C 或 Lisp 重写整个应用程序,也没有手写 C 扩展 。只是用一个简单的方法来整合 C 的速度和 C 数据类型到 Python函数中去
Cython 代码跟 Python 不一样,必须要编译。 编译经过两个阶段:(1) Cython 编译.pyx 文件为.c 文件 (2) C 编译器会把.c 文件编译成.so 文件.生成.so 文件后表示重写函数成功,可以在 python 代码中直接调用这个模块
Python代码
#p1.py
import math
def great_circle(lon1,lat1,lon2,lat2):
radius = 3956 #miles
x = math.pi/180.0
a = (90.0-lat1)*(x)
b = (90.0-lat2)*(x)
theta = (lon2-lon1)*(x)
c = math.acos((math.cos(a)*math.cos(b)) +
(math.sin(a)*math.sin(b)*math.cos(theta)))
return radius*c
Python代码
#p1_test.py
import timeit
lon1, lat1, lon2, lat2 = -72.345, 34.323, -61.823, 54.826
num = 500000
t = timeit.Timer("p1.great_circle(%f,%f,%f,%f)" % (lon1,lat1,lon2,lat2),
"import p1")
print "Pure python function", t.timeit(num), "sec"
Python代码
# 测试结果
Pure python function 2.25580382347 sec
Cython: 使用Python的math模块
Python代码#c1.pyx
import math
def great_circle(float lon1,float lat1,float lon2,float lat2):
cdef float radius = 3956.0
cdef float pi = 3.14159265
cdef float x = pi/180.0
cdef float a,b,theta,c
a = (90.0-lat1)*(x)
b = (90.0-lat2)*(x)
theta = (lon2-lon1)*(x)
c = math.acos((math.cos(a)*math.cos(b)) + (math.sin(a)*math.sin(b)*math.cos(theta)))
return radius*c
Python代码
# setup.py
from distutils.core import setup
from distutils.extension import Extension
from Cython.Distutils import build_ext
ext_modules=[
Extension("c1",
["c1.pyx"])
]
setup(
name = "Demos",
cmdclass = {"build_ext": build_ext},
ext_modules = ext_modules
)
python setup.py build_ext --inplace
Python代码
#c1_test.py
import timeit
lon1, lat1, lon2, lat2 = -72.345, 34.323, -61.823, 54.826
num = 500000
t = timeit.Timer("c1.great_circle(%f,%f,%f,%f)" % (lon1,lat1,lon2,lat2),
"import c1")
print "Pure python function", t.timeit(num), "sec"
Python代码
#执行结果:
Pure python function 1.87078690529 sec
Cython:使用C的math库
Python代码#c2.pyx
cdef extern from "math.h":
float cosf(float theta)
float sinf(float theta)
float acosf(float theta)
def great_circle(float lon1,float lat1,float lon2,float lat2):
cdef float radius = 3956.0
cdef float pi = 3.14159265
cdef float x = pi/180.0
cdef float a,b,theta,c
a = (90.0-lat1)*(x)
b = (90.0-lat2)*(x)
theta = (lon2-lon1)*(x)
c = acosf((cosf(a)*cosf(b)) + (sinf(a)*sinf(b)*cosf(theta)))
return radius*c
Python代码
#setup.py
from distutils.core import setup
from distutils.extension import Extension
from Cython.Distutils import build_ext
ext_modules=[
Extension("c2",
["c2.pyx"],
libraries=["m"]) # Unix-like specific
]
setup(
name = "Demos",
cmdclass = {"build_ext": build_ext},
ext_modules = ext_modules
)
python setup.py build_ext --inplace
Python代码
# c2_test.py
import timeit
lon1, lat1, lon2, lat2 = -72.345, 34.323, -61.823, 54.826
num = 500000
t = timeit.Timer("c2.great_circle(%f,%f,%f,%f)" % (lon1,lat1,lon2,lat2),
"import c2")
print "Pure python function", t.timeit(num), "sec"
Python代码
#执行结果
Pure python function 0.34069108963 sec
Cython:使用C函数
Python代码#c3.pyx
cdef extern from "math.h":
float cosf(float theta)
float sinf(float theta)
float acosf(float theta)
cdef float _great_circle(float lon1,float lat1,float lon2,float lat2):
cdef float radius = 3956.0
cdef float pi = 3.14159265
cdef float x = pi/180.0
cdef float a,b,theta,c
a = (90.0-lat1)*(x)
b = (90.0-lat2)*(x)
theta = (lon2-lon1)*(x)
c = acosf((cosf(a)*cosf(b)) + (sinf(a)*sinf(b)*cosf(theta)))
return radius*c
def great_circle(float lon1,float lat1,float lon2,float lat2,int num):
cdef int i
cdef float x
for i from 0 <= i < num:
x = _great_circle(lon1,lat1,lon2,lat2)
return x
C-sharp代码
#setup.py
from distutils.core import setup
from distutils.extension import Extension
from Cython.Distutils import build_ext
ext_modules=[
Extension("c3",
["c3.pyx"],
libraries=["m"]) # Unix-like specific
]
setup(
name = "Demos",
cmdclass = {"build_ext": build_ext},
ext_modules = ext_modules
)
python setup.py build_ext --inplace
Python代码
#c3_test.py
import timeit
lon1, lat1, lon2, lat2 = -72.345, 34.323, -61.823, 54.826
num = 500000
t = timeit.Timer("c2.great_circle(%f,%f,%f,%f)" % (lon1,lat1,lon2,lat2),
"import c2")
print "Pure python function", t.timeit(num), "sec"
Python代码
#测试结果
Pure python function 0.340164899826 sec
测试结论
Python代码# python代码
Pure python function 2.25580382347 sec
# Cython,使用Python的math模块
Pure python function 1.87078690529 sec
# Cython,使用C的math库
Pure python function 0.34069108963 sec
# Cython,使用纯粹的C函数
Pure python function 0.340164899826 sec
相关文章推荐
- python/Django下读写文件,等待,调用shell命令等技术总结
- python爬虫技术总结
- 使用扩展HibernateDaoSupport实现分页技术总结
- paip. 调试技术打印堆栈 uapi print stack java php python 总结.
- Python学习总结笔记(2)--类扩展小结
- python第三方扩展库及不同类型的测试需安装相对应的第三方库总结
- linux下python扩展包安装的一点总结
- 使用swig为python添加c扩展总结
- Python基础技术问题总结
- Python扩展包的安装方法总结
- Python基础技术问题总结
- python与爬虫技术总结
- 51CTO技术沙龙之从业务扩展的角度看Linux运维技术总结 推荐
- paip. 调试技术打印堆栈 uapi print stack java php python 总结.
- [网络技术] Python中的时间处理大总结 [
- Python相关技术学习总结
- sql server中扩展存储过程随笔(几个有用的PROCEDURE小总结)
- 常用ASP技术的总结
- 今天技术的总结
- 项目技术经验总结二:系统多风格的实现