最近想学习下Python的源码,希望写个系列博客,记录的同时督促自己学习。

Python源码目录

从Python.org中下载源代码压缩包并解压,我下载的是Python2.7.12,解压后:

对于主要的文件夹做出介绍:

Include:包含Python提供的所有头文件,如果需要自己使用C或者C++编写自定义模块扩展Python,就需要用到这里的头文件;

Lib: 包含Python自带的所有标准库,全部由Python语言编写;

Modules:包含了所有使用C语言编写的模块;

Parser:Python解释器中的Scanner和Parser(对Python代码进行词法分析和语法分析),这里还包含一些能根据Python语言的语法自动生成Python词法和语法功能的工具;

Objects:所有Python的内建对象;

Python:Python解释器中的Compiler和执行引擎部分,是Python运行的核心所在!!!

Python中的对象

  对象可以说是Python最核心的一个概念,在Python的世界里,一切都是对象。我们知道Python是由C编写的,C并不是一个面向对象语言,而由C编写的Python确实面向对象的,那么它的对象机制是如何实现的呢?

  对于人的思维,对象是可以形象描述的,但是对于计算机而言,对象是一个抽象的概念,计算机所知道的一切都是字节。关于对象,通常的说法是,对象是数据以及基于这些数据的操作的集合,在计算机中,一个对象实际就是把一片被分配的内存空间,且这片内存在更高层次可作为一个整体,这个整体就是一个对象。

  在Python中,对象就是C中的结构体在堆上申请的一块内存。

对象机制的基石——Pyobject
在Python中,所有的东西都是对象,而所有的对象都拥有一些相同的内容,Python中的这些内容都是在object.h中的Pyobject中定义的。

typedef struct _object {
PyObject_HEAD
} PyObject;

定长对象和变长对象

Python除了Pyobject对象之外,还有一个表示这类对象的结构体pyVarObject,pyVarobject其实就是对pyobject的一个扩展。
那么站在源码的角度上分析,变长对象是在pyVarobject中添加了可变长度数据的对象,也就是ob_size,定义了所容纳元素的个数。定长对象和变长对象的区别是:定长对象的不同对象占用的内存大小是一样的,变长对象的不同对象占用的内存可能是不一样的。比如整数对象'1'和'100'占用的内存大小都是sizeof(PyIntObject),而字符串对象"me"和"you"占用的内存大小就不一样。

Python对象的多态性

  面向对象中一个重要的特性是多态,那么Python是如何实现多态的呢?
  在Python创建一个对象时,会分配内存,进行初始化,然后Python内部会使用一个PyObject*变量来保存和维护这个对象,Python中的所有对象均是如此。比如创建一个PyIntObject对象(整数对象),不是通过PyIntObject *变量来保存和维护这个对象,而是通过PyObject *,正因为所有对象均如此,所以Python内部各个函数之间传递的都是一种范型指针(Pyobject*),而这个指针所指的对象究竟是什么类型的,我们是不知道的,只能从指针所指对象的ob_type域动态进行判断,而正是这个域,Python实现了多态。

引用计数

  和C或C++不同,Python选择使用语言本身负责内存的管理和维护,也就是垃圾收集机制,代替程序员进行繁重的内存管理工作,而引用计数刚好是Python垃圾收集机制的一部分。
  Python通过对一个对象的引用计数来管理和维护对象在内存中的存在与否。Python中的一切皆是对象,在所有的对象中有一个ob_refcent变量,维护这对象的引用计数,从而也决定该对象的创建与消亡。
在Python中,使用Py_INCREF(op)和Py_DECREF(op)两个宏来增加和减少一个对象的引用计数,在每一个对象创建的时候,Python提供了一个Py_NewReference(op)宏来将对象的引用计数初始化为1。
  当一个对象的引用计数为0时,与该对象对应的析构函数将被调用, 但是调用析构函数并不一定是调用free释放内存空间,为了避免频繁的申请、释放内存空间,Python中使用的是内存对象池,维护一定大小的内存对象池,调用析构函数时,对象占用的空间将归还到内存池中。

Python中的整数对象
  在Python的所有对象中,整数对象最简单且使用最频繁,故我们首先学习整数对象。关于整数对象的源码在Objects.intobjects.c中,整数对象是通过PyIntObject对象来完成的,在创建一个PyIntObject对象之后,就再也不能改变该对象的值了。定义为:

typedef struct {
prObject_HEAD;
long ob_ival;
}PyIntObject;

  可以看到,Python中的整数对象其实是对C中long的一个简单封装,也就是整数对象维护的数据的长度在对象定义时就已经确定了,就是C中long的长度。
  在Python中,整数的使用是很广泛的,对应的,它的创建和释放也将会很频繁,那么如何设计一个高效的机制,使得整数对象的使用不会成为Python的瓶颈?在Python中是使用整数对象的缓冲池机制来解决此问题。使用缓冲池机制,那意味着运行时的整数对象并不是一个个独立的,而是相关联结成一个庞大的整数对象系统了。
小整数对象
  在实际的编程中,数值比较小的整数,比如1,2,等等,这些在程序中是频繁使用到的,而Python中,所有的对象都存活在系统堆上,也就是说,如果没有特殊的机制,对于小整数对象,Python将一次次的malloc在堆上申请空间,然后free,这样的操作将大大降低了运行效率。
那么如何解决呢?Python中,对小整数对象使用了对象池技术。
那么又有一个问题了,Python中的大对象和小对象如何区分呢?嗯,Python中确实有一种方法,用户可以调整大整数和小整数的分界点,从而动态的确定小整数的对象池中应该有多少个小整数对象,但是调整的方法只有自己修改源代码,然后重新编译。
大整数对象
  对于小整数,小整数对象池中完全的缓存PyIntObject对象,对于其它对象,Python将提供一块内存空间,这些内存空间将由这些大整数轮流使用,也就是谁需要的时候谁使用。
  比如,在Python中有一个PyIntBlock结构,维护了一块内存,其中保存了一些PyIntObject对象,维护对象的个数也可以做动态的调整。在Python运行的某个时刻,有一些内存已经被使用,而另一些内存则处于空闲状态,而这些空闲的内存必须组织起来,那样,当Python需要新的内存时,才能快速的获得所需的内存,在Python中使用一个单向链表(free_list)来管理所有的空闲内存。

#define BLOCK_SIZE      1000    /* 1K less typical malloc overhead */
#define BHEAD_SIZE 8 /* Enough for a 64-bit pointer */
#define N_INTOBJECTS ((BLOCK_SIZE - BHEAD_SIZE) / sizeof(PyIntObject)) struct _intblock {
struct _intblock *next;
PyIntObject objects[N_INTOBJECTS];
}; typedef struct _intblock PyIntBlock; static PyIntBlock *block_list = NULL;
static PyIntObject *free_list = NULL;

创建
  现在,我们已经大体知道Python中整数对象系统在内存是一种怎样的结构了,下面将介绍一个个PyIntObject是怎样的从无到有的产生。主要分为两步:
  如果小整数对象池机制被激活,则尝试使用小整数对象池;如果不能使用小整数对象池,则使用通用整数对象池。

以PyInt_FromLong说明:

PyObject *
PyInt_FromLong(long ival)
{
register PyIntObject *v;
#if NSMALLNEGINTS + NSMALLPOSINTS > 0
if (-NSMALLNEGINTS <= ival && ival < NSMALLPOSINTS) {
v = small_ints[ival + NSMALLNEGINTS];
Py_INCREF(v);
#ifdef COUNT_ALLOCS
if (ival >= 0)
quick_int_allocs++;
else
quick_neg_int_allocs++;
#endif
return (PyObject *) v;
}
#endif
if (free_list == NULL) {
if ((free_list = fill_free_list()) == NULL)
return NULL;
}
/* Inline PyObject_New */
v = free_list;
free_list = (PyIntObject *)Py_TYPE(v);
PyObject_INIT(v, &PyInt_Type);
v->ob_ival = ival;
return (PyObject *) v;
}

Python源码分析(一)的更多相关文章

  1. Python源码分析(二) - List对象

    python中的高级特性之一就是内置了list,dict等.今天就先围绕列表(List)进行源码分析. Python中的List对象(PyListObject) Python中的的PyListObje ...

  2. Python 源码分析:queue 队列模块

    起步 queue 模块提供适用于多线程编程的先进先出(FIFO)数据结构.因为它是线程安全的,所以多个线程很轻松地使用同一个实例. 源码分析 先从初始化的函数来看: 从这初始化函数能得到哪些信息呢?首 ...

  3. Python源码分析之dis

    一.简单例子 def add(a, b): return a + b add_nums.py import foo a = [1, 'python'] a = 'a string' def func( ...

  4. Python源码分析

  5. 分位数回归及其Python源码

    分位数回归及其Python源码 天朗气清,惠风和畅.赋闲在家,正宜读书.前人文章,不得其解.代码开源,无人注释.你们不来,我行我上.废话少说,直入主题.o( ̄︶ ̄)o 我们要探测自变量 与因变量 的关 ...

  6. Python之美[从菜鸟到高手]--urlparse源码分析

    urlparse是用来解析url格式的,url格式如下:protocol :// hostname[:port] / path / [;parameters][?query]#fragment,其中; ...

  7. [python] 基于词云的关键词提取:wordcloud的使用、源码分析、中文词云生成和代码重写

    1. 词云简介 词云,又称文字云.标签云,是对文本数据中出现频率较高的“关键词”在视觉上的突出呈现,形成关键词的渲染形成类似云一样的彩色图片,从而一眼就可以领略文本数据的主要表达意思.常见于博客.微博 ...

  8. Python 进阶之源码分析:如何将一个类方法变为多个方法?

    前一篇文章<Python 中如何实现参数化测试?>中,我提到了在 Python 中实现参数化测试的几个库,并留下一个问题: 它们是如何做到把一个方法变成多个方法,并且将每个方法与相应的参数 ...

  9. python基础-11 socket,IO多路复用,select伪造多线程,select读写分离。socketserver源码分析

    Socket socket通常也称作"套接字",用于描述IP地址和端口,是一个通信链的句柄,应用程序通常通过"套接字"向网络发出请求或者应答网络请求. sock ...

随机推荐

  1. win10家庭版的defender注册表关闭和开启

    关闭方法: 打开“命令提示符(管理员)”,然后输入: reg add "HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Microsoft\Windows Defe ...

  2. 网络软中断与NAPI函数分析

    网卡只有rx硬中断,外设通过中断控制器向CPU发出有数据包来临的通知, 而没有tx硬中断,因为发送数据包是cpu向外设发出的命令. ixgbe驱动的rx软中断和tx软中断在同一个CPU上处理. htt ...

  3. shell中的常用通配符,字符类

    因为 shell 频繁 地使用文件名,shell 提供了特殊字符来帮助你快速指定一组文件名.这些特殊字符叫做通配符. 通配符         意义 * 匹配任意多个字符(包括零个或一个) ? 匹配任意 ...

  4. [Vue]组件——组件的data 必须是一个函数

    普通的Vue实例data是一个对象: data: { count: 0 } 组件的data是一个方法: data: function () { return { count: 0 } } 详情见官网: ...

  5. Java 练习题摘录

    2018-01-21 23:23:08 1.finally与return同时出现的情况 finally中有return语句则会使try...catch中的return语句失效 public stati ...

  6. 1.SpringMVC设计理念与DispatcherServlet

    SpringMVC作为Struts2之后异军突起的一个表现层框架,正越来越流行,相信javaee的开发者们就算没使用过SpringMVC,也应该对其略有耳闻.我试图通过对SpringMVC的设计思想和 ...

  7. Linux运维第二天:安装虚拟机软件及RHEL7.2 64位系统

    第一步:安装虚拟机 一路默认就行啦(还是要改下安装路径,嘿嘿) 第二步:安装RHEL7.2 64位系统 1.新建一个虚拟机 典型和自定义随便选(最后都可以调的) 一般大婶都喜欢选自定义(自己可以设置的 ...

  8. spoj-ANARC05H -dp

    ANARC05H - Chop Ahoy! Revisited! #dynamic-programming Given a non-empty string composed of digits on ...

  9. pyhon SyntaxError: Non-ASCII character '\xe8' in file xxx on line xx, but no encoding

    import math if __name__ == '__main__':    name1 = raw_input("请输入您的编号:")    print name1 完整的 ...

  10. 关闭多个screen

    由于开了很多个screen同时工作,关闭是一个一个比较麻烦,写个命令在这以便日后想不起来时可以用到. 1.先看看有多少个screen    screen -ls |awk '/Socket/'|awk ...