先笨办法学pythonn再学C/C++是一个怎样的决定?

编程初学者应该先写 c++还是Java还是Python?- 百度派
{{ mainData.uname }}
:{{ mainData.content }}
{{ prevData.uname }}
:{{ prevData.content }}
{{ mainData.uname }}
:{{ mainData.content }}
0 || contentType !== 1" class="separate-line">
:{{ item.content }}
编程初学者应该先写 c++还是Java还是Python?
问题说明(可选):
扫一扫分享到微信
我很认真认真的报告你,先学习c语言。确实c相对来说比较难,但是它就像你幼儿时期的爬行,是你学会走和跑的紧张底子!c语言中你要本身细致内存走漏,垃圾采取等等一系列紧张知识,而这些高级语言都帮你做了...
&&&&我很认真认真的报告你,先学习c语言。确实c相对来说比较难,但是它就像你幼儿时期的爬行,是你学会走和跑的紧张底子!c语言中你要本身细致内存走漏,垃圾采取等等一系列紧张知识,而这些高级语言都帮你做了,大概这一辈子你都不会知道,而这些确实是成为一个良好步伐员的必备知识,我这里说的是良好,而不是代码工!其次c的IDE一样平常都不强大,你才华真真千万领会得手敲代码的以为。&&&&java我发起做第二个学习的语言,java是面向东西的语言,更贴近人的思索,很多底层帮你封装好了,你不消太存眷底层实现。你可以从java中学谋面向东西的特点,网络编程等一系类知识。&&&&其次我发起python末了学习,大概和java一起学习,你掌握了c(不是夺目),对付这些都好学多了,语言都是雷同的,越今后你越会明白。python的初志便是快捷方便,种种百般的库能餍足你大部分的编程,而库内的知识点你不肯定必要知道,以是算是个“外貌”事变者!&&&&总结一下,我发起初学习底子但非常紧张的c做入门(要是你感兴趣可以深学),相识编程根本见解和底层原理,知道步伐在呆板里到底怎么运行的,然后学习java大概和python一起学习,它们可以让你更快速创建应用,末了看你的兴趣去学习深入此中一门大概多门语言!
扫一扫分享到微信
编程初学者应该先写 c++还是Java还是Python?
,才能进行回答
一个问题只能回答一次,请&nbsp点击此处&nbsp查看你的答案
1人关注了此问题现在最热门的是不是Python C++ Java?_百度知道
现在最热门的是不是Python C++ Java?
我有更好的答案
hiphotos.baidu.com/zhidao/wh%3D450%2C600/sign=0ae0cdca5ebd/7dd98dbe736d19645://c.hiphotos.baidu.com/zhidao/wh%3D600%2C800/sign=dda9be554df90bf7b305/7dd98dbe736d19645.com/zhidao/pic/item/7dd98dbe736d19645.jpg" target="_blank" title="点击查看大图" class="ikqb_img_alink"><img class="ikqb_img" src="http://c这是世界范围内的排行.com/zhidao/wh%3D600%2C800/sign=ccaab18e043e93105cacaf5/5d6034a85edf8db155d7b574e74d2.jpg" esrc="http://g.hiphotos.baidu.baidu。
采纳率:71%
来自团队:
最热门的是JAVA,没有其他了
为您推荐:
其他类似问题
您可能关注的内容
python的相关知识
&#xe675;换一换
回答问题,赢新手礼包&#xe6b9;
个人、企业类
违法有害信息,请在下方选择后提交
色情、暴力
我们会通过消息、邮箱等方式尽快将举报结果通知您。互联网诱惑太多,如何静下心来学习编程?
19:42:56 +08:00 · 2950 次点击
~~本人是一名高一在校生,今年刚初中毕业,各位大大表打我wwwww~~
一直对电脑比较感兴趣,初中的时候搞了点PHP,也就刚会点语法,不会就search+copy。。然后其余时间全浪费在瞎折腾和易语言上了。。易语言能写点像样软件了,会用一些系统API,学了点Inline hook、窗口自绘之类的,自己还给同学做游戏辅助用来着。。因为易语言快速开发易上手的特点,我基本上就是想到什么做什么,自己也高兴于完成的作品,这样学下去的。。后来随着接触面的日渐广泛,自己也明白易语言没前途,于是决定转学C++
以上不是重点
You know , C++刚开始的时候只能学些语法,做些控制台程序,也没易语言的高度封装,于是没法跟易语言一样一上来就能做GUI而想到什么做什么
于是现在根本没法静下心来学了QAQ
自己买了本《Essential C++》,但是根本读不下去,无法深入理解,只能硬着头皮逼着自己看,感觉十分浮躁,效率不高。
平时在家在学校都能安心,但是假期在家就无法安心(因为有机会接触电脑了。。ORZ)
如果在学校或者不能接触电脑的地方看书,就能安心读下去,但是学编程必须敲代码练习吧。。。So,十分矛盾纠结。。。
后来参加了在线MOOC学习,也有以上问题。。感觉就像逼着自己完成任务一样。。。没以前乐趣了。。
于是,问题来了:
有什么可以安心地高效学习编程的方法?
第 1 条附言 &·&
20:32:18 +08:00
感谢大家的回复,自己想明白了一些。。原来我应该放弃治疗...
27 回复 &| &直到
05:55:00 +08:00
& & 19:45:56 +08:00 via iPhone
你到底是要学编程还是要学C++?
& & 19:46:57 +08:00
1. 可以的话找几个伙伴一起努力进步
2. 可以考虑先试试 Python 这样的脚本语言 上手难度不大
& & 19:47:24 +08:00
@ C++不也是编程么 ORZ
语言不是重点吧,其实我也有学python,不过想以c++为主攻方向
& & 19:49:34 +08:00
所有一开始学习C++的人都这样想,学习VB的都能写个小软件了,为啥我还在那里天天对着控制台发呆?
既然你要安心高效的学习编程,那么我推荐你去学C#。难度适中,学习起来也不会像C++那样好长时间了还是面对控制台。
当然,如果你忍得住寂寞,坚持学C++,那么我建议你把书换成C++ primer。
& & 19:50:28 +08:00 via Android
我年轻时要是有人告诉我编程就好了。。。
我对这方面很感兴趣,但是到现在——大一,才真正接触到编程。。。
& & 19:52:46 +08:00 via iPhone
@ 是编程,但是学起来是两回事。
你现在高一,学习c++有什么特殊意义吗?如果没有的话,建议从简单的环境开始学习。php其实是个很好的开始,python也很好,要玩窗口的话c#不二选择。等你熟练了以后,再把c++剩下的部分捡起来就轻松多了。
& & 19:57:57 +08:00 via iPhone
现在就确定主攻方向我觉得太早了,而且计算机语言一直就不是什么主攻方向。普遍的要求是需要你用什么语言的时候你就应该能在短期时间内捡起来学会然后干活。比如说你会c++,但是有个项目突然要你用JavaScript+Haskell来做,你应当能很快上手开工而不是眼瞎。
& & 20:04:14 +08:00
用 C# 作學完 VB(或它的山寨者)的下一步不錯。學 C++ 之前要不要先學一下彙編和 C?
安心需要的不是方法,而是行動。就像掀開被子,你去問如何掀開被子只是在理性地拖延行動。尋找如何安心的方法的努力只會讓你更加無法安心,有這些時間,編程本身能帶給你的樂趣早就讓你忘記了一切誘惑與困難。
& & 20:06:46 +08:00
參見 爲什麼放棄治療會成爲一種治療:
& & 20:09:13 +08:00
@ 没什么特殊意义,只是自己一直很感兴趣编程,想将来从事这方面工作,不影响现在的情况下自学下为将来打下点基础
我不继续深入搞php原因之一就是Web前端是我短板,自己写不出完整UI。感觉自己学的太杂乱没一点条理系统,也不知道从哪开始学好,感觉浪费很多时间却没多大提升(不论是前端还是php后端)。看到其他初中高中的同龄人超出自己一大截,感觉自己弱爆了一直在虚度时间啊有木有
& & 20:30:26 +08:00
@ 谢谢,很有感触。这篇文章跟我很相似
& & 20:31:07 +08:00
我是来看头像的。
& & 20:34:37 +08:00
语言不是重点,重点是语言背后的思想
楼主很牛了,现在有一些20多岁的选手还都在复制粘贴HTML代码然后好为人师呢
最凶残的是还能被奉为大神手底下一帮小白跟他学习怎么用notepad写html
& & 20:55:33 +08:00 via Android
建议先学c,而不是cpp
how to think like a computer scientist
& & 21:11:25 +08:00
@ Web前端对于PHP来说是个循序渐进的国产,都菜过,时间长了该会的都会会的。
& & 21:14:34 +08:00 via iPhone
新手都小看控制台程序了。其实都是一样的。
& & 21:19:56 +08:00
大学狗看着你们。。。学Python吧,入门不错,我们学校正准备大一推行先学Python再学C
& & 21:23:36 +08:00
又手贱没写完就发出来了。用Python可以很快的做出一些小玩意儿,可以激发兴趣而且不是那么枯燥
& & 21:26:13 +08:00 via Android
我是来看大家推荐python的
& & 21:47:56 +08:00
其实语言本身不重要,你应该先思考一下编程带给你的是一种成就还是一种技能,前者只在于解决问题本身,可以说什么语言能够解决问题就是强大的,后者则在于编程语言已经变成了一个待解决的问题。前者驱动力是兴趣,后者则是意志。以你现在的年龄,希望你能够找到编程的乐趣,而不是深究语言本身。(个人观点,不喜勿喷)
ps:你已经很不错了,我在你这个年龄,还只知道打网游...惭愧阿...
& & 22:15:18 +08:00
放弃治疗就是要怎么做?
& & 00:25:57 +08:00 via iPhone
既然会易语言,学C++也该难度不大吧。
& & 00:45:26 +08:00
在 V2 看到 00 后发帖子 ... 这种感觉真的很奇妙 ...
& & 10:56:09 +08:00
@ 我是99年的..
& & 13:16:28 +08:00
@ 你太看得起易语言了
建议先学一下c,然后c++,然后Python什么的
还有系统能用linux不建议用win,如果要深入winapi就把我当空气吧
& & 13:21:30 +08:00
我觉得要不然你可以简单看一下 C艹 的语法然后去学学 Qt 啥的库…至少能达到“做出图形界面”这个要求(Qt 相当好了自带的 IDE 还能拖控件)。
C 和 C草 号称 segment fault 语言,用户程序员自己管理内存什么的…呃。
对了你是高中生,那干嘛不搞 OI 玩玩。
& & 05:55:00 +08:00
@ 我还以为易语言就是把C++翻译成了中文呢,看来不是这样啊。
& · & 2927 人在线 & 最高记录 3541 & · &
创意工作者们的社区
World is powered by solitude
VERSION: 3.9.8.0 · 69ms · UTC 07:57 · PVG 15:57 · LAX 23:57 · JFK 02:57? Do have faith in what you're doing.下次自动登录
现在的位置:
& 综合 & 正文
编写Python扩展(Extending Python with C or C++)
其实这是一篇译文,看官方文档的时候觉得不好对重点做标记,加上以后遗忘的时候看中文可以更快速的捡起来,所以在阅读的过程中就直接翻译出来记录在此了,借助于博客的一些编辑功能对重点做突出表现。
原文是python的官方文档:
这里有个大失误就是看的文档是3.4的,而我一般使用的Python都是2.6/2.7的,所以实际使用的时候得按照2.7的文档()来(否则会有错误,比如“错误:‘PyModuleDef_HEAD_INIT’未声明”)。
下面是一个基于python2.6.6完整的示例:
[dongsong@localhost spam]$ vpython --version
Python 2.6.6
[dongsong@localhost spam]$ pwd
/home/dongsong/python_study/spam
[dongsong@localhost spam]$ tree -a
├── setup.py
└── spammodule.c
0 directories, 2 files
[dongsong@localhost spam]$ cat setup.py
from distutils.core import setup, Extension
module1 = Extension('spam',
#include_dirs = ['/usr/local/include/python2.6/'],
#libraries = ['/usr/local/lib/'],
sources = ['spammodule.c'])
name = 'spam',
version = '1.0',
description = 'call system()',
ext_modules = [module1])
[dongsong@localhost spam]$ cat spammodule.c
#include&Python.h&
//自定义异常
static PyObject* SpamE
//模块的功能函数
static PyObject* spam_system(PyObject* self, PyObject* args)
const char*
if (!PyArg_ParseTuple(args, "s", &command))
return NULL;
sts = system(command);
if (sts & 0) {
PyErr_SetString(SpamError, "System command failed");
return NULL;
return PyLong_FromLong(sts);
static PyMethodDef SpamMethods[] = {
{"system", spam_system, METH_VARARGS,
"Execute a shell command."},
{NULL, NULL, 0, NULL}
//模块的结构体定义 v3.4
static struct PyModuleDef spammodule = {
PyModuleDef_HEAD_INIT,
SpamMethods
//模块初始化函数
//PyMODINIT_FUNC PyInit_spam(void) // v3.4
PyMODINIT_FUNC initspam(void)
//m = PyModule_Create(&spammodule); // v3.4
m = Py_InitModule("spam", SpamMethods);
if (m == NULL)
SpamError = PyErr_NewException("spam.error",NULL,NULL);
Py_INCREF(SpamError);
PyModule_AddObject(m,"error",SpamError);
[dongsong@localhost spam]$ vpython setup.py build
running build
running build_ext
building 'spam' extension
creating build
creating build/temp.linux-x86_64-2.6
gcc -pthread -fno-strict-aliasing -O2 -g -pipe -Wall -Wp,-D_FORTIFY_SOURCE=2 -fexceptions -fstack-protector --param=ssp-buffer-size=4 -m64 -mtune=generic -D_GNU_SOURCE -fPIC -fwrapv -DNDEBUG -O2 -g -pipe -Wall -Wp,-D_FORTIFY_SOURCE=2 -fexceptions -fstack-protector --param=ssp-buffer-size=4 -m64 -mtune=generic -D_GNU_SOURCE -fPIC -fwrapv -fPIC -I/usr/include/python2.6 -c spammodule.c -o build/temp.linux-x86_64-2.6/spammodule.o
creating build/lib.linux-x86_64-2.6
gcc -pthread -shared build/temp.linux-x86_64-2.6/spammodule.o -L/usr/lib64 -lpython2.6 -o build/lib.linux-x86_64-2.6/spam.so
t[dongsong@localhost spam]$ tree -a
├── build
│?? ├── lib.linux-x86_64-2.6
│?? │?? └── spam.so
│?? └── temp.linux-x86_64-2.6
└── spammodule.o
├── setup.py
└── spammodule.c
3 directories, 4 files
[dongsong@localhost spam]$ vpython setup.py install
running install
running build
running build_ext
running install_lib
copying build/lib.linux-x86_64-2.6/spam.so -& /home/dongsong/venv/lib64/python2.6/site-packages
running install_egg_info
Removing /home/dongsong/venv/lib64/python2.6/site-packages/spam-1.0-py2.6.egg-info
Writing /home/dongsong/venv/lib64/python2.6/site-packages/spam-1.0-py2.6.egg-info
[dongsong@localhost spam]$ vpython
Python 2.6.6 (r266:84292, Jul 10 :45)
[GCC 4.4.7
(Red Hat 4.4.7-3)] on linux2
Type "help", "copyright", "credits" or "license" for more information.
&&& import spam
&&& dir(spam)
['__doc__', '__file__', '__name__', '__package__', 'error', 'system']
&&& spam.system('ls -l')
drwxrwxr-x 4 dongsong dongsong 4096 7月
21 15:55 build
-rw-rw-r-- 1 dongsong dongsong
21 15:55 setup.py
-rw-rw-r-- 1 dongsong dongsong 1025 7月
21 15:55 spammodule.c
1. Extending Python with C or C++
C编写的扩展模块源码文件构成:
1.功能函数 spam_system
2.模块的方法表(Method Table)SpamMethods
3.模块的初始化函数(Initialization Function)PyInit_spam
4.模块定义结构体(definition structure) spammodule
如果会c语言编程的话,往python中添加新的内建模块(built-in modules)会很简单。有两件事情是这些扩展模块可以做而直接用python无法做的:实现新的内建对象类型(built-in object types);调用c库函数和系统调用。
为了支持扩展,Python API(Application Programmers Interface)定义了一批函数、宏和变量供我们访问Python运行时系统(Python run-time system)的大部分方面。include头文件“Python.h”就可以把Python的API引入到源文件里面来。
如果你的用例要调用C库函数或者系统调用,你应该考虑使用ctypes模块而不是编写自定义的c代码。ctypes不仅可以让你的python代码与C代码交互,而且移植性更好。
1.1 A Simple Example
让我们创建一个叫spam的扩展模块,并且我们说我们要创建一个调用C库函数system的Python接口。我们要这个函数可以从Python中像下面这样调用:
&&&import spam
&&&status = spam.system("ls -l")
根据历史规律(约定成俗),如果一个模块名字是spam,那么实现它的c源码文件一般是spammodule.c;如果模块名字很长,比如spammify,那么c源码文件可以是spammify.c。
我们文件的第一行是:
#include &Python.h&
这条语句引入Python API.
注意:因为python可能要定义一些预处理器的定义(影响一些系统的标准头文件),所以我们应该在include任何标准头文件之前include Python.h。
Python.h中定义的所有用户可见的符号都有个前缀Py或者PY,除了那些定义在标准头文件里面的。Python.h include了少量几个标准头文件(stdio.h, string.h, errno.h和stdlib.h),一则为了方便,二则因为这些标准头文件在python解释器中被广泛的使用。如果这些头文件在你的系统中不存在,它(Python.h?)会直接声明malloc(),free()和realloc()。
下一步我们添加到我们的模块文件中来的是C函数,这个函数会在Python语句spam.system(string)被执行时被调用:
static PyObject *
spam_system(PyObject *self, PyObject *args)
const char *
if (!PyArg_ParseTuple(args, "s", &command))
return NULL;
sts = system(command);
return PyLong_FromLong(sts);
这里有一个从Python参数列表(例如“ls -l”)到C函数参数的直观翻译。C函数(spam_system)一般有两个参数,按照习俗叫做self和args.
self参数,对于模块级别(module-level)的函数,self指向模块对象;对于一个方法,它指向对象实例。
arg参数,它是一个指向Python元组对象的指针,这个元组对象里面是参数。元组的每个元素对应着函数调用的参数列表中的一个参数。参数是Python对象---为了在我们的C函数里面用它们做点事儿,我们必须要把他们转换成C数据。Python API中的函数PyArg_ParseTuple()会检查参数类型并把他们转换成C数据。它使用一个模板字符串来确定参数需要的类型(也就是要存储转换后的数据的C变量的类型)。更多细节稍后再说。
PyArg_ParseTuple()返回true:如果所有参数类型正确并且数据存储到了传入的对应的变量上(变量地址已经传进去了)。PyArg_ParseTuple()返回false(zero):如果一个无效的参数列表被传进去。后面这种情况,会抛出一个对应的异常,所以被调用的函数会立马返回NULL(就像我们在例子中看到的一样)。
1.2. Intermezzo: Errors and Exceptions
Python中的一个重要约定是这样的:当一个函数失败的时候,Python解释器设置一个异常条件(环境)并返回错误值(一般是空指针)。异常被存储在解释器内部的一个静态全局变量上;如果这个变量为空(NULL)就表明没有异常发生。再用另一个全局变量存储异常对应的信息(raise的第二个参数)。还有第三个变量存放导致错误发生的python代码的调用栈的回溯(stack traceback)。C中的这三个变量等效于Python中sys.exc_info()的返回值。了解这些对于理解错误的传递非常重要。
PyErr_SetString(),最常用的设置异常的函数。参数是异常对象和一个C字符串。异常对象一般是预定义对象,比如PyExc_ZeroDivisionError. C字符串是错误的原因,它被转换成一个Python的字符串对象并作为异常对应的信息存储起来("asociated value" of the exception)。
另一个很有用的函数是PyErr_SetFromErrno(),只需要一个异常参数,通过检查全局变量errno构建异常对应的信息。最普遍的函数是PyErr_SetObject(),需要两个对象参数,一个是异常另一个是对应的信息。我们不需要对传入这些函数的对象调用Py_INCREF()。
PyErr_Occurred()可以检测以否已经有异常被设置了。它返回当前异常独享或者NULL(如果还没有异常发生)。一般来讲,我们不需要调用这个函数来看是否有错误发生,因为返回值(函数返回值)会告诉我们。
当一个函数f调用另一个函数g并检测到后者失败了,f应该返回一个错误值(一般是NULL或者-1)。f不再调用PyErr_*()之类的函数---因为g已经调过了---f的调用者也返回一个错误值而不调用PyErr_*()之类的函数,以此类推---最详细的错误原因已经在第一个检测到它的函数里面做了汇报了。当错误到大Python解释器的主循环(main loop),就会终止当前正在执行的python代码并试图找到一个Python程序员设置异常处理(an exception handler)。
要忽略一个失败的函数调用设置的异常,异常条件(环境)只能用显式调用PyErr_Clear()来清除。C代码只在它不想把错误传递给解释器而想完全靠自己处理它(可能要尝试其他事情或者假装没有错误发生)的时候才调用PyErr_Clear()。
每个失败的malloc()调用必须返回一个异常---malloc()(或者realloc())的直接调用者(direct caller)必须调用PyErr_NoMemory()并返回一个失败标示(错误码)。所有的对象创建函数(比如,PyLong_FromLong())已经做过这种处理了,所以这个规则是针对那些直接调用malloc()的情况而言的。
注意,对于PyArg_ParseTuple()和类似函数(friends)的异常,返回一个整数状态值的函数一般会在成功的时候返回一个整数或者0,在失败的时候返回-1,跟Unix系统调用类似。
最后,当返回错误标识的时候要小心清理垃圾(对我们已创建的对象调用Py_XDECREF()或者Py_DECREF()).
抛出哪个异常完全由我们决定。跟所有的内建Python异常相对应的C对象都已经预先声明好了,比如PyExc_ZeroDivisionError,这些我们可以直接使用。当然,我们应该明智的选择异常---不要使用PyExc_TypeError来表示有文件打不开(用PyExc_IOError才对)。如果参数列表有错,PyArg_ParseTuple()函数一般会抛出PyExc_TypeError。如果我们有一个数值必须在一个特定范围内或者必须符合某些条件的参数,那么出错时PyExc_ValueError比较合适。
我们也可以自定义一个新的异常:我们要在文件(模块的C代码)开头出声明一个静态对象(PyObject)变量:
static PyObject* SpamE
然后在模块的初始化函数(PyInit_spam())中用一个异常对象初始化它。
PyMODINIT_FUNC
PyInit_spam(void)
PyObject *m;
m = PyModule_Create(&spammodule);
if (m == NULL)
return NULL;
SpamError = PyErr_NewException("spam.error", NULL, NULL);
Py_INCREF(SpamError);
PyModule_AddObject(m, "error", SpamError);
注意,这个异常对象的Python名字是spam.error.PyErr_NewException()函数可以Exception(内建异常的文档有描述)为基类(除非另一个非NULL的基类传递进去)创建一个类。
还得注意的是,SpamError变量保留了对新创建的异常类的引用;这是故意的!因为异常可以被其他外部的代码从这个模块移除,保留对这个类的引用可以确保它不会被丢弃(导致SpamError变成一个悬空的指针)。如果变成了悬空的指针,抛这个异常的C代码会导致一个core dump或者其他意想不到的问题。
我们稍后再讨论把PyMODINIT_FUNC作为函数返回类型的用法。
spam.error异常可以在我们的扩展模块中被抛出:如下所示通过调用PyErr_SetString():
static PyObject *
spam_system(PyObject *self, PyObject *args)
const char *
if (!PyArg_ParseTuple(args, "s", &command))
return NULL;
sts = system(command);
if (sts & 0) {
PyErr_SetString(SpamError, "System command failed");
return NULL;
return PyLong_FromLong(sts);
1.3. Back to the Example
回到我们例子中的函数,我们现在可以理解下面这语句了:
if (!PyArg_ParseTuple(args, "s", &command))
return NULL;
如果参数列表检测到错误它返回NULL。否则,参数的字符串数据会被拷贝给局部变量command。这是一个指针赋值,我们不应该修改它所指向的字符串,所以在标准C里面,变量command应该恰当的声明为const char *command.
下一条语句是对Unix函数system()的调用,把我们刚从PyArg_ParseTuple()获取的字符串传给它:
sts = system(command);
我们的spam.system()函数必须把sys的值作为一个Python对象返回去。这个由函数PyLong_FromLong()完成:
return PyLong_FromLong(sts);
在这里,它会返回一个整数对象(是的,即使是整数,在python中也是堆里面的对象)。
c模块提供的函数返回值必须是Python对象,如果C函数不返回有用的值(return void),对应的Python函数应该返回None(可由宏Py_RETURN_NONE代劳):
Py_INCREF(Py_None);
return Py_N
Py_None是Python的特别对象None在C中的名字,它是一个真实(和空指针不一样)的对象。
1.4. The Module’s Method Table and Initialization Function
spam_system()怎么被Python程序调用是一定会说的。只是,首先,我们需要把它的名字和地址列进一个方法表(method table)里:
static PyMethodDef SpamMethods[] = {
{"system",
spam_system, METH_VARARGS,
"Execute a shell command."},
{NULL, NULL, 0, NULL}
/* Sentinel */
注意,第三个参数(METH_VARARGS),这个标记告诉解释器该函数调用按照约定会使用C函数。一般情况下这里会用METH_VARARGS或METH_VARARGS|METH_KEYWORDS, 0表示使用了PyArg_ParseTuple()的一个过时的变体。
使用单纯的METH_VARARGS,函数要求Python程序员传递元组参数以便通过PyArg_ParseTuple()解析;这个函数的更多的信息下面给出。
如果要传关键字参数给函数就应该把METH_KEYWORDS设置上。这种情况下,C函数应该接受一个关键字的字典作为第三个参数(PyObject*)。用PyArg_ParseTupleAndKeywords()来解析参数。
模块的结构体定义:
static struct PyModuleDef spammodule = {
PyModuleDef_HEAD_INIT,
/* name of module */
spam_doc, /* module documentation, may be NULL */
/* size of per-interpreter state of the module,
or -1 if the module keeps state in global variables. */
SpamMethods
模块的初始化函数:
PyMODINIT_FUNC
PyInit_spam(void)
return PyModule_Create(&spammodule);
模块的结构体定义必须传递给模块的初始化函数。初始化函数必须用PyInit_name()的格式命名,name就是模块的名称。初始化函数是模块文件中唯一的非静态(non-static)条目(在C的源码中用static可以限定相关条目只可在本文件中被访问,可见模块的初始化函数是唯一的出口/对外接口)。
当python程序第一次import模块spam时模块的初始化函数PyInit_spam()会被调用。
模块的初始化函数内部调用PyModule_Create()返回一个module对象,并根据模块的方法表(一个由PyMethodDef结构体构成的数组)把函数对象插入到这个新建的module。初始化函数需要返回一个modeule对象给它的调用者,然后该module对象会被插入到sys.modules中。
在程序中嵌入Python解释器时,PyInit_spam()函数不会自动被调用除非在PyImport_Inittab表中有该条目。要把spam模块添加到初始化表,需要用PyImport_AppendInittab():
main(int argc, char *argv[])
/* Add a built-in module, before Py_Initialize */
PyImport_AppendInittab("spam", PyInit_spam);
/* Pass argv[0] to the Python interpreter */
Py_SetProgramName(argv[0]);
/* Initialize the Python interpreter.
Required. */
Py_Initialize();
/* Optional alternatively,
import can be deferred until the embedded script
imports it. */
PyImport_ImportModule("spam");
Python源码中Modules/xxmodule.c是一个更可靠的模块示例,这个文件可以用来作为一个编写扩展模块的模板。
1.5. Compilation and Linkage
编译成动态加载模块:
linux下编译 Building C and C++ Extensions with distutils() ---& page down !
windows下编译 Building C and C++ Extensions on Windows()
编译成Python解释器固定的部分(不要动态加载):
修改配置重新编译解释器
UNIX把我们的文件(比如spammodule.c)放到Python源码的Modules/目录下;在Modeules/Setup.local中添加一行"spam spammodule.o";在顶级目录下运行make.如果我们的模块需要链接其他额外的库,额外的库也需要加到前述的那一行中,如"spam spammodule.o
1.6. Calling Python Functions from C
目前为止我们一直专注在如何让Python调用C函数。反过来,C代码调用Python函数也是很有意义的。对于那些支持回调函数的库来讲,这个尤其需要。如果一个C接口使用了回调,对应的Python一般需要为Python程序员提供一个回调机制;而实现则需要用C的回调函数调用Python的回调函数。当然,可以想到,“C代码调用Python函数”还会有其他的用途。
幸运的是,Python解释器很容易递归调用,并且有一个标准接口来调用Python函数。
调用一个Python函数是很容易的:python程序必须以某种方式给我们传递Python函数对象(function object)。我们需要提供一个函数(或者其他什么接口)来做这个。当我们提供的这个函数被调用时,它把一个指向Python函数对象(对它Py_INCREF()的时候要小心)的指针保存到一个全局变量(a global variable)里面---或者其他我们觉得合适的地方。例如,下面是一个模块定义的示例:
static PyObject *my_callback = NULL;
static PyObject *
my_set_callback(PyObject *dummy, PyObject *args)
PyObject *result = NULL;
PyObject *
if (PyArg_ParseTuple(args, "O:set_callback", &temp)) {
if (!PyCallable_Check(temp)) {
PyErr_SetString(PyExc_TypeError, "parameter must be callable");
return NULL;
Py_XINCREF(temp);
/* Add a reference to new callback */
Py_XDECREF(my_callback);
/* Dispose of previous callback */
my_callback =
/* Remember new callback */
/* Boilerplate to return "None" */
Py_INCREF(Py_None);
result = Py_N
这个函数(my_set_callback)在注册到解释器时必须使用METH_VARARGS标记;这个在前面The Module's Method and Initialization Function()里讲过了。PyArg_ParseTuple()函数和它的参数在Extraction
Parameters in Extension Functions)里有文档。
宏Py_XINCREF()和Py_XDECREF()用于增加/减少一个对象的引用计数,它们操作空指针也是安全的(上述例子中temp不是空指针)。引用计数的文档在这里。
然后,是时候调用函数了,我们可以调用C函数PyObject_CallObject().这个函数有两个参数,都是指向任意Python对象的指针:Python函数,和参数列表。参数列表必须是一个元组对象(a tuple object),元组的长度就是参数的个数。要无参调用Python函数,传递NULL或者一个空元组进去;要带参调用,传递一个元组进去。Py_BuildValue(),当它的格式化字符串由"括号"和"0或其他字符(格式化符号)"组成时,该函数返回一个元组。例如:
PyObject *
PyObject *
arg = 123;
/* Time to call the callback */
arglist = Py_BuildValue("(i)", arg);
result = PyObject_CallObject(my_callback, arglist);
Py_DECREF(arglist);
PyObject_CallObject()返回一个Python对象指针:这是Python函数的返回值。PyObject_CallObject()对于它的参数而言是“引用计数中性”(reference-count-neutral,我的理解是对传入内部的对象不加也不减其引用计数)的。在上述例子中,一个新的元组被创建出来作为参数列表,并且在调用结束后立马用Py_DECREF()减引用计数。
PyObject_CallObject()的返回值是新的(new):要嘛是一个全新的对象,要嘛是一个已存在的、已增加了引用计数的对象。所以,除非我们要把它保存到全局变量里面去,否则我们应该对这个结果调用Py_DECREF(),即使我们对结果值不感兴趣。
然而,在减引用前,检查一下这个返回值是否为NULL也是很重要的。如果为空,表明Python函数异常终止了。如果调用PyObject_CallObject()的C代码是被Python程序调用的,那么C代码现在应该返回一个错误标示给它的Python调用方,这样python解释器就可以打印调用栈信息了,或者调用方python代码可以处理异常。如果PyObject_CallObject()返回值不可能为空或者不需要关心,那么应该调用PyErr_Clear()清除异常。例如:
if (result == NULL)
return NULL; /* Pass error back */
...use result...
Py_DECREF(result);
Python回调函数(callback function)如果需要的话,我们可能还得提供一个参数列表给PyObject_CallObject().某些情况下,参数列表也需要由Python程序通过指定回调函数的那个接口(上述的my_set_callback(..)函数)给出。然后跟函数对象一样被存储和使用。其他一些情况下,我们需要构建一个新的元组作为函数列表。最简单的方式是调用Py_BuildValue().例如,如果我们传递一个不可分割的事件代码(an
integral event code),我们可以用下述代码:
PyObject *
arglist = Py_BuildValue("(l)", eventcode);
result = PyObject_CallObject(my_callback, arglist);
Py_DECREF(arglist);
if (result == NULL)
return NULL; /* Pass error back */
/* Here maybe use the result */
Py_DECREF(result);
注意,Py_DECREF(arglist)的位置紧跟在函数调用后面、在错误检查之前!还要注意的是,严格来讲这段代码不完整(健全):Py_BuildValue()可能耗尽内存,而这需要做检查。
我们也可以通过PyObject_Call()以带关键字参数的形式调用函数,Py_Object_Call()支持参数(位置参数)和关键字参数。就像上述例子一样,我们使用Py_BuildValue()来构建字典:
PyObject *
dict = Py_BuildValue("{s:i}", "name", val);
result = PyObject_Call(my_callback, NULL, dict);
Py_DECREF(dict);
if (result == NULL)
return NULL; /* Pass error back */
/* Here maybe use the result */
Py_DECREF(result);
1.7. Extracting Parameters in Extension Functions
在扩展函数里面提取参数(位置参数)
int PyArg_ParseTuple(PyObject *arg, char *format, ...);
arg必须是一个元组对象,它包含一个参数列表,从Python传递给C函数。format必须是一个格式化串,语法解释见Python/C API 参开手册中的Parsing arguments and building values().剩余参数必须是变量地址,变量类型有格式化串决定。
注意,PyArg_ParseTuple()检查Python参数是否有要求的类型,但不检查后面那些参数(C变量地址)的有效性:如果我们在这里犯错,我们的代码可能会崩溃或者至少会有内存被非法改写。所以要小心。
还要注意的是,所有传给这个函数的Python对象的引用是借出去的;不要减少他们的引用计数。(Note that any Python object references which are provided to the caller are do not decrement their reference count! 到底什么意思??有歧义啊~)
一些示例如下:
#define PY_SSIZE_T_CLEAN
/* Make "s#" use Py_ssize_t rather than int. */
#include &Python.h&
const char *s;
ok = PyArg_ParseTuple(args, ""); /* No arguments */
/* Python call: f() */
ok = PyArg_ParseTuple(args, "s", &s); /* A string */
/* Possible Python call: f('whoops!') */
ok = PyArg_ParseTuple(args, "lls", &k, &l, &s); /* Two longs and a string */
/* Possible Python call: f(1, 2, 'three') */
ok = PyArg_ParseTuple(args, "(ii)s#", &i, &j, &s, &size);
/* A pair of ints and a string, whose size is also returned */
/* Possible Python call: f((1, 2), 'three') */
const char *
const char *mode = "r";
int bufsize = 0;
ok = PyArg_ParseTuple(args, "s|si", &file, &mode, &bufsize);
/* A string, and optionally another string and an integer */
/* Possible Python calls:
f('spam', 'w')
f('spam', 'wb', 100000) */
int left, top, right, bottom, h,
ok = PyArg_ParseTuple(args, "((ii)(ii))(ii)",
&left, &top, &right, &bottom, &h, &v);
/* A rectangle and a point */
/* Possible Python call:
f(((0, 0), (400, 300)), (10, 10)) */
ok = PyArg_ParseTuple(args, "D:myfunction", &c);
/* a complex, also providing a function name for errors */
/* Possible Python call: myfunction(1+2j) */
1.8. Keyword Parameters for Extension Functions
在扩展函数里面提取关键字参数
int PyArg_ParseTupleAndKeywords(PyObject *arg, PyObject *kwdict, char *format, char *kwlist[], ...);
arg和format参数跟PyArg_ParseTuple()相同。kwdict参数一个关键字的字典,是从Python接收到的第三个参数(PyObject* self, Pyobject* args, PyObject* keywds)。kwlist参数是一个以NULL结尾的字符串列表,它标识关键字参数(参数名);这些名字跟format中的类型信息从左到右相匹配。如果调用成功,PyArg_ParseTupleAndKeywords()返回true,否则它返回false并抛出一个对应的异常。
注意,使用关键字参数时,嵌套的元组不能被解析。传递kwlist中不存在的关键字参数会抛出TypeError异常。
下面是一个基于Geoff Philbrick()的例子、使用关键字参数的示例模块:
#include "Python.h"
static PyObject *
keywdarg_parrot(PyObject *self, PyObject *args, PyObject *keywds)
char *state = "a stiff";
char *action = "voom";
char *type = "Norwegian Blue";
static char *kwlist[] = {"voltage", "state", "action", "type", NULL};
if (!PyArg_ParseTupleAndKeywords(args, keywds, "i|sss", kwlist,
&voltage, &state, &action, &type))
return NULL;
printf("-- This parrot wouldn't %s if you put %i Volts through it.\n",
action, voltage);
printf("-- Lovely plumage, the %s -- It's %s!\n", type, state);
Py_INCREF(Py_None);
return Py_N
static PyMethodDef keywdarg_methods[] = {
/* The cast of the function is necessary since PyCFunction values
* only take two PyObject* parameters, and keywdarg_parrot() takes
{"parrot", (PyCFunction)keywdarg_parrot, METH_VARARGS | METH_KEYWORDS,
"Print a lovely skit to standard output."},
{NULL, NULL, 0, NULL}
/* sentinel */
static struct PyModuleDef keywdargmodule = {
PyModuleDef_HEAD_INIT,
"keywdarg",
keywdarg_methods
PyMODINIT_FUNC
PyInit_keywdarg(void)
return PyModule_Create(&keywdargmodule);
1.9. Building Arbitrary Values
这是跟PyArg_ParseTuple()类似(地位相当,counterpart)的函数。声明如下:
PyObject *Py_BuildValue(char *format, ...);
该函数跟PyArg_ParseTuple()一样会识别一堆的格式化信息,但是参数不能是指针,只能是数值。函数返回一个新的Python对象,适合于从C函数返回给Python调用方。
跟PyArg_ParseTuple()不同的一点:后者要求它的第一个参数是元组(因为python参数列表在内部总是以元组的形式呈现),而Py_BuildValue()并不总是构建元组。只有当格式化字符串包换两个或更多个格式化单元(format units)的时候,Py_BuildValue()才会创建一个元组出来。如果格式化串为空,它返回N如果格式化串只包含一个格式化单元,它返回格式化单元描述的那个对象。要强制它返回一个大小为0或者1的元组,需要用括号把格式化串括起来。
示例(左边是调用,右边是Python数据结果):
Py_BuildValue("")
Py_BuildValue("i", 123)
Py_BuildValue("iii", 123, 456, 789)
(123, 456, 789)
Py_BuildValue("s", "hello")
Py_BuildValue("y", "hello")
Py_BuildValue("ss", "hello", "world")
('hello', 'world')
Py_BuildValue("s#", "hello", 4)
Py_BuildValue("y#", "hello", 4)
Py_BuildValue("()")
Py_BuildValue("(i)", 123)
Py_BuildValue("(ii)", 123, 456)
(123, 456)
Py_BuildValue("(i,i)", 123, 456)
(123, 456)
Py_BuildValue("[i,i]", 123, 456)
[123, 456]
Py_BuildValue("{s:i,s:i}",
"abc", 123, "def", 456)
{'abc': 123, 'def': 456}
Py_BuildValue("((ii)(ii)) (ii)",
1, 2, 3, 4, 5, 6)
(((1, 2), (3, 4)), (5, 6))
1.10. Reference Counts
在C/C++里面,程序员需要负责从堆动态申请和归还内存。在C里面,用malloc()和free()来完成。在C++里面,用new和delete有同样效果,下面我们的讨论仅限于C。
用malloc()申请的每块内存最终都应该调用一次free()把内存归还给可用内存的池子里。在正确的时机调用free()是非常重要的。如果一个内存块的地址被遗忘了并且对应的free()没有调用,那么在整个程序结束之前它占据的内存是不能被重复利用的。这就是内存泄露。另一方面,如果程序对一个内存块调用了free()却还是继续使用这块内存,那么在使用另一个malloc()来重用这块内存时就发生冲突了。这和引用未初始化的数据一样不会有好结果---core
dumps,错误的结果,诡异的崩溃。
常见的内存泄露原因是代码写得不严谨(unusual paths though the code)。举个例子,一个函数可能申请一块内存,做一些计算,然后释放内存。现在函数的需求变了,可能对计算增加一个错误条件(an error condition)的检测并从函数中提前返回。这里提前返回时就很容忘记释放申请的内存块,尤其是当这个返回是后加到代码里面去的。这种泄露,一旦引入,经常会令人讨厌的延续很长一段时间:错误退出只是所有调用中的一小部分,并且现代化的机器都有大量的虚拟内存,所有这种泄露只会在一个长时间运行且频繁使用内存泄露函数的进程里暴露出来。所以,从编码习惯和策略上最小化这种错误以防止内存泄露的发生是非常重要的。
因为Python大量使用malloc()和free(),Python需要一种策略来避免内存泄露和(非法)使用已释放的内存。引用计数就是被选中的策略。原理很简单:每个对象包含一个计数,对该对象的引用被存储到某个地方时对象的计数增加,对该对象的引用被删除时对象的计数减小。当计数变成0,说明最后的一个对该对象的引用也被删除了,那么该对象可以释放(内存)了。
另一种策略被成为自动垃圾收集(automatic garbage collection)。(有时,引用计数也被成为垃圾收集策略,所以我用“自动(automatic)”来区分这两个策略)。自动垃圾收集策略最大的优点是用户不用显式的调用free().(另一种宣称的优势是在速度或内存使用上的改善---然而,这个是没有事实依据的)。自动垃圾收集策略相对于C的劣势是,很难有移植性好(portable)的自动垃圾收集器,而引用计数可以方便的实现移植性(只要malloc()和free()可用就行了---这是标准C需要保证的)。可能未来某天一个C语言的、具备足够移植性性的自动垃圾收集器会出现。但目前为止,我们还是得靠引用计数而生。
Python使用的是传统引用计数的实现,它还提供了环形检测(检测引用出现环)。这使得应用程序不用担心创建直接或间接的环形引用;这些是单独使用引用计数来实现垃圾收集的缺点。环形引用由一些引用他们自身(可能不是直接的)的对象构成,所以环上的每个对象有一个非零的引用计数。典型的引用计数的实现无法回收环形引用上任何对象的内存,无法回收被“环上的对象”引用的“对象”的内存,即使已经没有其他地方引用这些对象了。
环形检测可以检测垃圾环并回收它们,只要它们没有实现Python的结束函数(finalizers,我理解的是析构函数)(__del__())。如果存在这些结束函数,检测器会把环用gc模块暴露(expose,我理解的是丢出去)出去(具体来讲,是gc模块里面的garbage变量)。gc模块也提供了一种运行监测器的方式(collect()函数),也提供了运行时停用检测器的接口和功能,和配置文件效果一样。环形检测器被认为是一个可选(非必需)组件;虽然它默认被引入(be
included),但是在Unix平台(包含Mac OS X)它是可以通过编译阶段加--without-cycle-gc选项到配置脚本来禁掉的。如果环形检测器用这种方式禁掉了,gc模块将不在可用。
1.10.1. Reference Counting in Python
两个宏(Py_INCREF(x)和Py_DECREF(x))处理引用计数的增加和减小。Py_DECREF()会在引用计数变成零的时候释放对象。为了灵活性(For flexibility),它不直接调用call()函数---而是,它通过该对象的类型对象(the object's type object)的一个函数指针来做函数调用。为了这一点(和其他一些原因),每个对象必须包含一个指向它的类型对象的指针。
现在遗留了一个大问题:什么时候调用Py_INCREF(x)和Py_DECREF(x)?我们先来介绍几个术语。没人拥有(own)一个对象;你可以拥有对对象的一个引用。一个对象的引用计数就是拥有对它的引用的数量。当一个引用不再需要时,引用的拥有者要负责调用Py_DECREF()。引用的拥有关系可以传递。有三种方式可以丢掉一个已经拥有的引用:传递它,存储它,或者调用Py_DECREF().忘记丢掉一个已拥有的引用会造成内存泄露。
借用(borrow)一个对象的引用也是可以的。借用者不能调用Py_DECREF().借用者持有对象的时间不能比这个引用的拥有者(即被借者)持有对象的时间还长。在引用的拥有者已经丢弃该引用以后,借用者还继续使用借来的引用有使用已释放内存的风险,这个应该完全避免。
借用一个引用相对于拥有一个引用的优点是,我们不需要关心丢弃引用的事情---换句话说,用一个借来的引用当函数提前退出的时候我们没有泄露内存的风险。借用引用的缺点是,在一个微妙的情况下,在看上去正确的代码里,一个借来的引用可能已经由被借者把拥有关系掉丢了,而这里还在拿着它pia~pia~的用。
一个借来的引用可以通过调用Py_INCREF()而转变成一个拥有的引用。这个不影响被借者的状态---这种方式会创建一个新的被拥有的的引用,并且给予完整的拥有者权利(新的拥有者也必须像之前的拥有者一样合理的丢弃引用)。
1.10.2. Ownership Rules
当一个对象的引用被传进或传出一个函数,引用的拥有关系(所有权)是否跟着传递必须有明确说明,且作为函数接口规范的一部分。
概述,函数返回的引用一般是传递所有权(例外:PyTuple_GetItem(),PyList_GetItem(),PyDict_GetItem(),PyDict_GetItemString(),PyImport_AddModule()),传入函数的引用一般是借用所有权(例外:PyTuple_SetItem(),PyList_SetItem())。
大部分返回对象引用的函数会传递所有权。特别是所有创建新对象的函数,比如PyLong_FromLong()和Py_BuildValue(),都把引用的所有权传递给了接收者。即使对象不是新创建的,我们还是会收到对象的一个新的引用的所有权。比如,PyLong_FromLong()维护着一个常用数据的缓存并可能返回一个引用指向一个被缓存的条目。
很多从其他对象中提取对象的函数也会传递引用的所有权,比如PyObject_GetAttrString().这里情况还是不够清晰,因为少数几个常见的函数存在例外:PyTuple_GetItem(),PyList_GetItem(),PyDict_GetItem(),和PyDict_GetItemString()全部从元组(tuple),列表(list)和字典(dict)中返回借用的引用。
PyImport_AddModule()函数也返回借用的引用,即使实际上它返回的对象可能是它新创建的:这是有可能的,因为一个对象的已拥有引用(an owned reference)已存储在sys.modules.
当我们把一个对象引用传递进另一个函数时,一般来讲,那个函数会从我们这里借用引用---如果它需要存储它,它会调用Py_INCREF()来把自己变成一个引用的拥有者。这条规则也有两个重要的例外:PyTuple_SetItem()和PyList_SetItem().这些函数会拿走传递给他们的对象的所有权---即使他们运行失败!(注意,PyDict_SetItem()和类似函数(friends)不拿走引用所有权---他们是正常的("normal"))
当Python调用C函数时,C函数从它的调用者那里借来参数的引用。调用者保留对对象引用的所有权,所以借来的引用的生命期只能持续到这个函数返回。只有当这个借来的引用必须要存储或者做传递时,它才会通过调用Py_INCREF()被转变成有拥有权的引用。
从C函数返回给Python的对象引用必须是一个有拥有权的引用---拥有权从函数内传递给了它的调用者。
1.10.3. Thin Ice
这里有几个看上去很无害但实际却会出问题的对借用引用的使用案例。这几种情况都不得不做解释器的隐式调用,而这些会导致引用的拥有者丢弃引用。
第一个也是最重要的一个情况是在借用一个列表的元素的引用(a reference to a list itm)时对一个不相干的对象使用Py_DECREF()。比如:
bug(PyObject *list)
PyObject *item = PyList_GetItem(list, 0);
PyList_SetItem(list, 1, PyLong_FromLong(0L));
PyObject_Print(item, stdout, 0); /* BUG! */
这个函数先是借来一个list[0]的引用,然后把list[1]的值用0替换掉,最后打印借来的引用。看上去没问题,是吧?但是有问题!
让我们对控制流跟踪到PyList_SetItem()内部。列表拥有它所有元素的引用,所以当第一个元素(item 1)被替换时,列表必须丢弃掉原来那第一个元素。现在我们假设原来的第一个元素是一个用户自定义类的实例(an instance of a user-defined class),然后我们再假设那个类定义了一个__del__()方法。如果这个实例有一个引用计数为1,丢弃它会调用它的__del__()方法。
因为它是用Python编写的,__del__()函数会执行任意的Python代码。那么它有可能会做些事情让item的引用在bug()里面失效么?你打赌!假设传递到bug()的列表是可以被那个__del__()方法访问的,它可以执行一条一句类似于"del list[0]"的语句,并且假定这是对那个对象的最后一个引用,那么它会释放内存,因此item无效了。
既然你知道代码的问题,解决办法很简单:临时增加引用计数。正确版本如下:
no_bug(PyObject *list)
PyObject *item = PyList_GetItem(list, 0);
Py_INCREF(item);
PyList_SetItem(list, 1, PyLong_FromLong(0L));
PyObject_Print(item, stdout, 0);
Py_DECREF(item);
这是一个真实的故事。有个老版本的Python包含各种这样的BUG,并导致一些人花费大量的时间用C调试器来查找为什么他的__del__()方法会失败...
第二个借用引用的案例是一个涉及线程的变体。正常来讲,Python解释器的多线程相互不会干扰,因为有一个全局锁保护Python的整个对象空间。然而,用宏Py_BEGIN_ALLOW_THREADS可以临时释放这个锁,用宏Py_END_ALLOW_THREADS可以重新请求它(加锁)。在阻塞IO调用时这是很常见的,当等待IO完整的时让其他线程使用处理器。很明显,下面的函数有前述例子一样的问题:
bug(PyObject *list)
PyObject *item = PyList_GetItem(list, 0);
Py_BEGIN_ALLOW_THREADS
...some blocking I/O call...
Py_END_ALLOW_THREADS
PyObject_Print(item, stdout, 0); /* BUG! */
1.10.4. NULL Pointers
一般来讲,使用对象引用作为参数的函数不希望我们给它们传空指针(NULL pointers),如果你这么干就会dump core(or cause later core dumps). 一般来讲,返回对象引用的函数如果返回NULL只表明有异常发生了。不要测试NULL参数的原因是,函数经常把他们接收的对象传递给其他函数---如果每个函数都要做空指针测试,那将会有很多的多余的测试,代码运行会慢很多。
只在“source”测试NULL会更好一些,“source”:当收到一个可能为NULL的指针,比如,malloc()或者一个可能抛出异常的函数的返回值。
宏Py_INCREF()和Py_DECREF()不检测空指针---而,他们的变体Py_XINCREF()和Py_XDECREF()会做检测。
检查一个特定对象的类型的宏(Pytype_Check())不检查空指针---再说一遍,简言之,会有很多代码并会运行缓慢。这个宏也没有做NULL检测的变体。
C函数调用机制保证传递给C函数的参数列表(在例子中是args)永远不为NULL---事实上他保证args一定是一个元组。
让空指针越狱跑到Python程序员手里去是一个严重的错误。
1.11. Writing Extensions in C++
用C++编写扩展模块也是可以的。一些限制如下。如果主程序(python解释器)使用C编译器编译和连接的,有构造器的全局或静态对象无法使用。如果主程序是用C++编译器链接(linked)的这就不是问题了。要被Python解释器(尤其是模块初始化函数)调用的函数必须用“extern "c"”声明。python的头文件没必要用“extern "c" {...}”包围---如果定义了符号__cplusplus(所有最新的C++编译器都定义过这种符号),python头文件就已经使用这种格式了。
1.12. Providing a C API for an Extension Module
很多扩展模块只提供python使用的新函数和类型,但是有时候扩展模块里面的代码对其他扩展模块来讲也很有用。比如,一个扩展模块可以实现一个新的类型“collection”,它类似于list但无序。就像标准Python的list类型有一个允许扩展模块创建和操纵list的C API一样,这个新的collection类型也应该有一堆c函数来让其他扩展模块直接操作collection。
第一眼貌似很容易:只用写函数(当然了,不要做static声明),提供一个恰当的头文件,和C API文档。实际上,如果所有扩展模块都静态链接到Python解释器的话这么干确实没问题。当模块被作为共享库来使用时,定义在一个模块中的符号对另一个模块可能就不可见了。“是否可见”的细节由操作系统决定;有些系统为Python解释器和所有扩展模块(例如,windows)使用一个全局的名字空间,而其他系统在模块连接时要求一个显式的列表存储引入的符号(an explicit list of imported symbols),或者要求提供一个不同策略的选择(大多数的unix)。即使符号是全局可见的,我们要调用的函数所在的模块也有可能还没有加载呢!
要想有够好的可移植性,就不应该对符号的可见性做任何假设。这意味着为了避免和其他扩展模块的名称冲突,扩展模块里面的所有符号应该被声明为static,除了模块的初始化函数。这意味着要被其他扩展模块访问的符号必须以一种不同的方式导出。
Python提供了一种特别的机制把C级别(C-level)的信息(指针)从一个扩展模块传递到另一个:Capsules(胶囊?).一个Capsule是一个Python数据类型,它存储(携带,stored)一个指针(void*).Capsules只能通过他们的C API创建和访问,但是他们可以像任何其他Python对象一样到处传递。尤其是(specially),它们可以赋值给扩展模块的名字空间中的一个名字。其他扩展模块可以import这个模块,获得这个名字对应的值,并接着通过Capsule获得指针。
有很多方式可以使得Capsules用于导出一个扩展模块的C API。每个函数可以有它自己的Capsule,或者所有C API指针可以存储到一个数组,这个数组的地址通过一个Capsule公布出去。存储和获取指针的各种任务可以用不同的方式在当前模块和其他client模块之间分发。
不论你选哪种方法,合理的命名你的Capsules是很重要的。函数PyCapsule_New()使用一个name参数(const char *);我们可以传递一个NULL名字进去,但我们被强烈的建议使用一个非空的名字。合理的命名Capsules在一定程度上可以保证运行时的类型安全(type-safety);如果不命名的话,一个模块没法告诉另一个模块这里的Capsule.
需要特别说明的是,用于暴露C API的Capsules应该按下面的风格(convention)给一个名字:
modulename.attributename
PyCapsule_Import()函数可以很容易的加载一个由Capsule提供的C API,但要求Capsule的名字符合这种风格。这样(behavior)让C API的使用者更加肯定他们加载的Capsule包含正确的C API.
下面的例子论证了把大部分复杂工作(负担)放在出口(export)模块上的做法,对于常用库模块是很合理的。它把所有C API指针(在例子中只有一个指针)存储到一个(元素类型为)void指针的数组里面,这个数组将会成为Capsule的值。跟模块对应的头文件给出一个宏来import模块和获取C API指针;client模块只有在访问C API之前需要调用这个宏。
要出口(export)的模块是从spam模块修改而来的。函数spam.system()不直接调用C库函数system(),而是调用函数PySpam_System(),当然了,这个函数会做一些更复杂的事情(比如把“spam”添加到每个命令里)。这个PySpam_System()函数也会导出给其他扩展模块。
PySpam_System()是一个纯的C函数,跟其他所有的一样声明了static:
static int
PySpam_System(const char *command)
return system(command);
spam_system()改了点细节:
static PyObject *
spam_system(PyObject *self, PyObject *args)
const char *
if (!PyArg_ParseTuple(args, "s", &command))
return NULL;
sts = PySpam_System(command);
return PyLong_FromLong(sts);
在模块的开始、紧跟着下面这行:
#include "Python.h"
必须再添加两行:
#define SPAM_MODULE
#include "spammodule.h"
#define是要告诉头文件,它正在被include到一个出口模块(exporting module),而不是一个client模块。最后,模块的初始化函数必须小心的初始化C API指针数组:
PyMODINIT_FUNC
PyInit_spam(void)
PyObject *m;
static void *PySpam_API[PySpam_API_pointers];
PyObject *c_api_
m = PyModule_Create(&spammodule);
if (m == NULL)
return NULL;
/* Initialize the C API pointer array */
PySpam_API[PySpam_System_NUM] = (void *)PySpam_S
/* Create a Capsule containing the API pointer array's address */
c_api_object = PyCapsule_New((void *)PySpam_API, "spam._C_API", NULL);
if (c_api_object != NULL)
PyModule_AddObject(m, "_C_API", c_api_object);
注意,PySpam_API被声明为否则,当PyInit_spam()终止时指针数组会消失!
大部分的工作在spammodule.h中,如下所示:
#ifndef Py_SPAMMODULE_H
#define Py_SPAMMODULE_H
#ifdef __cplusplus
extern "C" {
/* Header file for spammodule */
/* C API functions */
#define PySpam_System_NUM 0
#define PySpam_System_RETURN int
#define PySpam_System_PROTO (const char *command)
/* Total number of C API pointers */
#define PySpam_API_pointers 1
#ifdef SPAM_MODULE
/* This section is used when compiling spammodule.c */
static PySpam_System_RETURN PySpam_System PySpam_System_PROTO;
/* This section is used in modules that use spammodule's API */
static void **PySpam_API;
#define PySpam_System \
(*(PySpam_System_RETURN (*)PySpam_System_PROTO) PySpam_API[PySpam_System_NUM])
/* Return -1 on error, 0 on success.
* PyCapsule_Import will set an exception if there's an error.
static int
import_spam(void)
PySpam_API = (void **)PyCapsule_Import("spam._C_API", 0);
return (PySpam_API != NULL) ? 0 : -1;
#ifdef __cplusplus
#endif /* !defined(Py_SPAMMODULE_H) */
为了访问函数PySpam_System(),client模块要做的是必须在它的初始化函数里调用函数(或者宏)import_spam():
PyMODINIT_FUNC
PyInit_client(void)
PyObject *m;
m = PyModule_Create(&clientmodule);
if (m == NULL)
return NULL;
if (import_spam() & 0)
return NULL;
/* additional initialization can happen here */
这种方式的主要缺点是spammodule.h文件太复杂了。然而,每个要导出的函数的基本结构是一样的,所以这个只用学一次就行了。
最后需要提醒的的是,Capsules提供了附加的功能,这个对Capsule里面存储的指针做内存申请和释放时尤其有用。细节在Python/C API参考手册的Capsules部分()和Capsules的实现里面(源码Include/pycapsule.h和Object/pycapsule.c)。
3. Building C and C++ Extensions with distutils
从Python1.4开始,Python在Unix平台提供一个专门的make file来为编译动态链接扩展和定制解释器创建make file.从Python2.0开始,这个机制(Makefile.pre.in相关的东西和设置文件)不再支持了。创建定制的解释器很少被使用,并且扩展模块可以用distutils创建。
用distutils创建扩展模块要求机器上已经安装了distutils,distutils已经被包含到python2.x里面了并且可单独被python1.5使用。自从distutils也支持创建二进制包以后,用户没必要一定用编译器和和distutils来安装扩展了。
一个distutils包包含一个驱动脚本,setup.py.这是一个纯Python文件,在大部分简单的情况下,这个文件看上去是这样的:
from distutils.core import setup, Extension
module1 = Extension('demo',
sources = ['demo.c'])
setup (name = 'PackageName',
version = '1.0',
description = 'This is a demo package',
ext_modules = [module1])
拿着这个setup.py和一个demo.c文件,运行
python setup.py build
将会编译demo.c,并在build目录生成一个名字为demo的扩展模块。在不同的系统上,模块文件最终会在子目录build/lib.system中,并且可能有一个像demo.so或demo.pyd一样的名字。
在setup.py,所有的执行是通过调用setup
【上篇】【下篇】

我要回帖

更多关于 笨办法学python 的文章

 

随机推荐