Python 核心编程读书笔记 Day3

Posted on 日 13 七月 2014 in readings • Tagged with corepython, reading-notes, python • 1 min read

今天阅读的章节是 8 和 9 章,前面的章节已经介绍了 Python 的基本的数据类型,这两章分别介绍了 Python 的条件 循环语句和文件类型。

第八章:条件和循环

这章主要就是介绍 Python 中的条件和循环语句,Python 中的条件语句有 if-else,而循环则有 while 和 for。要点:

1.if 语句有 if-else 和 if-elif-elif-else 模式;

2.Python 中也存在条件表达式,和其他语言的不同,是利用 if 实现的:X if C else Y;

3.Python 中的 while 和其他语言的类似,而 for 循环则不一样,for 循环可以遍历可迭代对象;

4.在遍历迭代器的时候,for 循环会调用迭代器的 next 方法,并且在遇到 StopIteration 异常结束遍历;

5.range(start, stop, step=1) 函数可以生成一个列表;

6.sorted 和 zip 函数返回一个列表,而 reversed 和 enumerate 函数则返回一个迭代器;

7.else 同样可以用在 while 和 for 循环语句中,在循环结束后执行,break 则会跳出这个 else;

8.迭代器对象需要实现 next 和 __iter__ 方法;

9.列表解释:[expr for iter_var in iterable],返回列表;

10.生成器表达式:(expr for iter_var in iterable);

第九章:文件和输入输出

本章主要关注 Python 中的文件对象及输入和输出方面,下面是要点:

1.文件只是连续的字节序列;

2.可以用 open 或者 file 函数打开或者创建文件,这两个函数类似;

3.文件对象的 readlines 方法将会将该文件所有行都加载到内存中,打开大文件不太友好;

4.xreadlines 是以迭代的方式每次读取文件的一行,不过现在可以直接对文件对象进行迭代达到一样的效果;

5.readline 函数不会去除读取到的行的换行符,writelines 也不会自动添加换行符;


Python 核心编程读书笔记 Day2

Posted on 五 11 七月 2014 in readings • Tagged with corepython, reading-notes, python • 1 min read

今天主要阅读了 5 - 7 章的内容,继续总结每章的内容及要点。

第五章:数字

本章介绍 Python 中的各种数字类型及其运算符和处理数字的内建函数。要点如下:

1.Python 中的数字类型包括整型,长整型,布尔型,双精度浮点型,十进制浮点型和复数,都是不可变类型,对数字类型变量的变 更都会产生一个新的对象;

2.现在的 Python 支持整型自动转换为长整型,不会溢出;

3.Python 中只采用了双精度浮点型,不实现单精度浮点型,如果需要进行银行等系统编写可以考虑使用 Decimal 模块;

4.Python 中不同类型数字运算转换规则:存在复数转换为复数,否则存在浮点数则转换为浮点数,否则存在长整数则转换为长整 数,否则都是普通整数;

5.divmod 函数用于数值计算,返回一个包含商和余数的元组;

6.round 函数对数值进行四舍五入取整,返回一个浮点数;

7.chr 函数将 ASCII 值的数字转换为 ASCII 字符,ord 则相反;

第六章:序列:字符串,列表和元组

这一章关注的是 Python 中的序列类型,这些类型的特点是其成员有序排序,可以通过下标以类似偏移量的方式访问其成员,具体 来说这样的序列类型有三个:字符串,列表和元组。本章详细地介绍了这三个序列类型的操作符内建函数和特性等内容,以下为要 点:

1.序列类型可以使用 innot in 来判定某个元素是否属于一个序列;

2.对于序列使用 + 连接符会导致一个新的序列对象产生;

3.序列类型支持切片操作,可以使用 seq[start:stop:step] 来进行;

4.enumerate 函数接受一个可迭代对象,同样返回一个可迭代的 enumerate 对象,内容为之前对象的 index 和 item;

5.字符串是不可变类型,Python 中没有字符类型,可以用长度为 1 的字符串来表达这个概念;

6.Python 格式化字符:%[(name)][flags][width].[precision]typecode;

7.Python 格式化字符默认右对齐,- 改为左对齐,默认填充空格;

8.r 添加在字符串前表示为原始字符串,不需要对特殊字符进行转义;

9.Unicode 字符串 encode 为 str 字符串,str 字符串 decode 为 Unicode 字符串;

10.列表是可变类型,支持添加插入或者删除元素,并不会产生新的元素;


Python 核心编程读书笔记 Day1

Posted on 四 10 七月 2014 in readings • Tagged with corepython, reading-notes, python • 1 min read

今天主要阅读了 1 - 4 章的内容,下面总结下每章的主要内容和一些要点。

第一章:Python 核心

书本的开始只是一些关于 Python 的常识性的内容,包括但不限于起源、各个特性、各个系统的安装 方式还有其他语言(C 语言之外)的实现方式,下面是一些要点:

1.Python 是一门解释性的语言,但是却有个编译成字节码的编译过程,这一点和 Java 是类似的, 因为编译成字节码可以得到性能上的增强;

2.标准的 Python 官方发行的是 C 实现的 Python 版本,被称为 CPython,此外也有其他语言实现 的版本,比如 Java 实现的 Jython,运行在 JVM 上,可以利用到 JVM 的 JIT 技术,并且可以使用 Java 的类库。此外还有 C# 语言实现的 IronPython,可以运行在 .NET 及 Mono 环境上。还有一个 基于 CPython 修改的 Stackless Python ,这个版本对 CPython 解释器进行了大量的修改,实现了 用户级别的微线程。

第二章:快速入门

第二章非常简略地过了一遍 Python 的一些特性和语言结构数据类型等,以下是要点:

1.用 "Docstring" 或者 """Docstring""" 在模块,类或者函数起始添加可以实现运行时访问这个文 档字符串;

2.** 是乘方运算符,//是取比商小的最大整数运算;

3.Python 支持复数数字类型,形式类似 4 + 5j;

4.元组和列表都是可以保存任意数量任意类型的 Python 对象的容器对象,并且都是从 0 开始索引 访问元素,元组可以看成只读的列表,两者都支持切片;

5.Python 通过缩进来区分代码块;

6.可以通过列表解释来生成一个列表;

第三章:Python 基础

这章的主要内容是基本的 Python 语法,也介绍了标识符,变量和关键字等内容。要点如下:

1.可以通过 \ 连接多行的 Python 代码,也可以在含有小括号,中括号和花括号的时候跨行;

2.Python 中,对象是通过引用传递的,将一个对象赋值给一个变量,就是将这个对象的引用赋给 这个变量;

3.Python 中支持增量赋值 +=,但不支持 ++ 这种自增符;

4.Python 中支持多元赋值,可以同时将多个对象赋给多个变量,这种方式的赋值等号两边其实是两个元组;

5.Python 中下划线对解释器有特殊的意义, _xx 表示模块私有,__xxx__ 表示系统定义的


Python 类对象与实例对象源码分析

Posted on 三 02 十月 2013 in python • Tagged with python, source-reading • 8 min read

一个有趣的现象

最近在翻 Python 的 Tutorial 的对象一章,随手在 Python 的交互 Shell 中敲了几段代码测试一下,发现了一个有趣的情况。代码如下:

    >>> class TestCls(object):
    ...     
    ...     def say_hi(self):
    ...         print 'Hi!'
    ... 
    >>> t = TestCls()
    >>> t.say_hi()
    Hi!
    >>> t.ins_new_var = 101
    >>> t.ins_new_var
    101
    >>> TestCls.ins_new_var
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    AttributeError: type object 'TestCls' has no attribute 'ins_new_var'
    >>> TestCls.new_var = 100
    >>> TestCls.new_var
    100
    >>> t.new_var
    100

这段代码中,定义了一个类 TestCls ,然后实例化了一个 TestCls 的对象 t。在 Python 中,一切皆对象,这是老生长谈了。而 Python 中的对象还有另外一个特性,就是可以在创建之后修改这个对象的属性和方法。如上所示,我们可以在创建了一个类对象 TestCls 和一个实例对象 t 之后,修改这两个对象,给它们分别添加了 new_varins_new_var 属性。从上面的运行结果可以看到,当我们给实例对象 t 添加属性 ins_new_var 之后,类对象 TestCls 中访问不了这个属性,但是对于类对象 TestCls 添加的新属性 new_var ,这个类对象的实例 t 却可以访问到。

从 Python 代码的这个表现,我们可以推测出一些事情。那就是 Python 中,对一个对象的属性的访问会首先在这个对象的命名空间搜索,如果找不到,那就去搜索这个对象的类的命名空间,直到找到,然后取值,或者抛出没有这个属性的异常。很明显, Python 中一个对象的实例,同时还共享了这个对象的命名空间。如下:

    >>> dir(t)
    ['__class__', '__delattr__', '__dict__', '__doc__', '__format__',
    '__getattribute__', '__hash__', '__init__', '__module__', '__new__',
    '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__',
    '__str__', '__subclasshook__', '__weakref__', 'ins_new_var', 'new_var',
    'say_hi']
    >>> dir(TestCls)
    ['__class__', '__delattr__', '__dict__', '__doc__', '__format__',
    '__getattribute__', '__hash__', '__init__', '__module__', '__new__',
    '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__',
    '__str__', '__subclasshook__', '__weakref__', 'new_var', 'say_hi']
    >>>

可以看到,dir 函数搜索到的实例对象 t 和类对象 TestCls 的基本一致,但是区别在于 t 比 TestCls 多了一个 ins_new_var

    >>> t.__dict__
    {'ins_new_var': 101}
    >>> TestCls.__dict__
    dict_proxy({'__module__': '__main__', 'say_hi': <function say_hi at
    0xb771e95c>, '__dict__': <attribute '__dict__' of 'TestCls' objects>,
    '__weakref__': <attribute '__weakref__' of 'TestCls' objects>, '__doc__':
    None, 'new_var': 100})
    >>> t.new_var = 100
    >>> t.__dict__
    {'ins_new_var': 101, 'new_var': 100}
    >>>

从这里看到,当我们试图对 t.new_var 进行赋值时,t 的 __dict__ 增加了一个 new_var

上面的推测是否正确?也许直接去查看源码会得到答案。在本文中, Python 的源码均指 CPython 源码,版本为 2.7.4。

注1:一般是代码片段在上,分析在下。

数据结构

CPython 是 C 写的(很明显),类对象和实例对象的数据结构都是 struct,定义在 CPython 源码目录的 Include/classobject.h 中:

typedef struct {
    PyObject_HEAD
    PyObject    *cl_bases;  /* A tuple of class objects */
    PyObject    *cl_dict;   /* A dictionary */
    PyObject    *cl_name;   /* A string */
    /* The following three are functions or NULL */
    PyObject    *cl_getattr;
    PyObject    *cl_setattr;
    PyObject    *cl_delattr;
    PyObject    *cl_weakreflist; /* List of weak references */
} PyClassObject;

typedef struct {
    PyObject_HEAD
    PyClassObject *in_class;    /* The class object */
    PyObject      *in_dict; /* A dictionary */
    PyObject      *in_weakreflist; /* List of weak references */
} PyInstanceObject;

这两个结构体并不复杂,除了所有 Python 对象都有的 PyObject_HEAD 宏之外,类对象 PyClassObject 中还有几个属性,分别是: cl_bases ,保存了这个类对象的所有父类(如果有的话),这个属性是一个元组;cl_dict ,一个字典,保存的是属于这个类对象的属性和方法;cl_name ,保存的是这个类对象的名称,此外还有几个对象 cl_getattr, cl_setattr, cl_delattr ,。而实例对象则有 in_class 表示从哪个类对象实例化而来,还有 in_dict 同样是一个字典对象,保存了这个实例对象的属性和方法。可以看到,一个类的实例对象保存了这个实例对象实例化自哪个类对象。

PyObject_HEAD 的相关定义如下:

 /* Define pointers to support a doubly-linked list of all live heap objects. */
#define _PyObject_HEAD_EXTRA            \
struct _object *_ob_next;           \
struct _object *_ob_prev;

/* PyObject_HEAD defines the initial segment of every PyObject. */
#define PyObject_HEAD                   \
_PyObject_HEAD_EXTRA                \
Py_ssize_t ob_refcnt;               \
struct _typeobject *ob_type;

可以看到这两个宏定义了 Python 中一个对象的常见属性,包括对象类型 ob_type 和对象的引用计数 ob_refcnt,这是因为 Python 的 GC 方式是引用计数。

创建函数

在 Python 中对于类对象 (PyClassObject) 和实例对象 (PyInstanceObject) 的相关函数有很多,在这里我们只是简单分析下创建类对象及实例对象的函数和关于查找属性部分的函数。

注2:这里对这几个函数的代码引用不是完全的。

实例对象的创建函数

首先是创建类对象的函数:

static PyObject *
class_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
{

创建类对象的函数是 class_new ,参数是类型 type,还有多个参数元组对象 args 和多个关键字参数字典对象 kwds。

PyObject *name, *bases, *dict;
static char *kwlist[] = {"name", "bases", "dict", 0};

这里新建了几个 PyObject 类型的指针,分别是 name, bases 和 dict ,分别用来保存类对象的名称,继承的父类和属性方法字典。此外还有一个字符串数组 kwlist。

if (!PyArg_ParseTupleAndKeywords(args, kwds, "SOO", kwlist,
                                 &name, &bases, &dict))
    return NULL;
return PyClass_New(bases, dict, name);

然后这里是调用 PyArg_ParseTupleAndKeywords 函数,这个函数的主要效果是解析参数 args 和 kwds ,得到创建新的类对象的参数 bases,dict,name,然后调用真正创建一个类对象的函数 PyClass_New

PyObject *
PyClass_New(PyObject *bases, PyObject *dict, PyObject *name)
     /* bases is NULL or tuple of classobjects! */
{

PyClass_New 函数的有三个参数,分别是父类们 bases,类的属性方法字典 dict 和 类的名称 name。

接下来很长的一段代码都是对参数的解析及检查参数是否合法,比如 name 必须是一个字符串, dict 必须是一个字典等等,在这里略去。

if (PyDict_GetItem(dict, docstr) == NULL) {
    if (PyDict_SetItem(dict, docstr, Py_None) < 0)
        return NULL;
}
if (PyDict_GetItem(dict, modstr) == NULL) {
    PyObject *globals = PyEval_GetGlobals();
    if (globals != NULL) {
        PyObject *modname = PyDict_GetItem(globals, namestr);
        if (modname != NULL) {
            if (PyDict_SetItem(dict, modstr, modname) < 0)
                return NULL;
        }
    }
}

检查参数 dict 中是否有 __doc____module__ 这两个键,如果 __doc__ 不存在则设置并将其值设置为 Py_None,如果 __module__ 也不存在则获取当前范围的全局变量,从中取得 __module__ 所对应的值,赋给这个新类对象的 __module__

if (bases == NULL) {
    bases = PyTuple_New(0);
    if (bases == NULL)
        return NULL;
}
else {
    Py_ssize_t i, n;
    PyObject *base;
    if (!PyTuple_Check(bases)) {
        PyErr_SetString(PyExc_TypeError,
                        "PyClass_New: bases must be a tuple");
        return NULL;
    }
    n = PyTuple_Size(bases);
    for (i = 0; i < n; i++) {
        base = PyTuple_GET_ITEM(bases, i);
        if (!PyClass_Check(base)) {
            if (PyCallable_Check(
                (PyObject *) base->ob_type))
                return PyObject_CallFunctionObjArgs(
                    (PyObject *) base->ob_type,
                    name, bases, dict, NULL);
            PyErr_SetString(PyExc_TypeError,
                "PyClass_New: base must be a class");
            return NULL;
        }
    }
    Py_INCREF(bases);
}

检查 bases 参数是否为空,如果为空则新建一个值为 0 的元组赋给 bases。不为空,则 bases 应该是一个类对象的元组,依次对这个元组中的类对象进行检测,是否为类对象,如果不是类对象,则检测是否可调用 (callable) ,然后返回相应的错误信息或者一个可调用函数对象的执行结果(可调用)。

最后如果 bases 参数合法,这个参数对象的引用计数加一。

if (getattrstr == NULL) {
    getattrstr = PyString_InternFromString("__getattr__");
    if (getattrstr == NULL)
        goto alloc_error;
    setattrstr = PyString_InternFromString("__setattr__");
    if (setattrstr == NULL)
        goto alloc_error;
    delattrstr = PyString_InternFromString("__delattr__");
    if (delattrstr == NULL)
        goto alloc_error;
}

getattrstrsetattrstrdelattrstr 是三个全局的 static PyObject 指针变量,上面这一段分别给它们赋值字符串对象。

    op = PyObject_GC_New(PyClassObject, &PyClass_Type);
    if (op == NULL) {
alloc_error:
        Py_DECREF(bases);
        return NULL;
    }

给这个类对象分配内存,这个内存是在堆分配的而且受到 CPython 的 GC 管理的。

op->cl_bases = bases;
Py_INCREF(dict);
op->cl_dict = dict;
Py_XINCREF(name);
op->cl_name = name;
op->cl_weakreflist = NULL;

将三个参数分别赋给这个新建的类对象 op。

    op->cl_getattr = class_lookup(op, getattrstr, &dummy);
    op->cl_setattr = class_lookup(op, setattrstr, &dummy);
    op->cl_delattr = class_lookup(op, delattrstr, &dummy);
    Py_XINCREF(op->cl_getattr);
    Py_XINCREF(op->cl_setattr);
    Py_XINCREF(op->cl_delattr);
    _PyObject_GC_TRACK(op);
    return (PyObject *) op;
}

然后分别设置这个新类对象的 getattr , setattr 和 delattr 函数,增加这几个函数的引用计数等等,最后返回这个新建的类对象的指针。

实例对象的创建函数

实例对象 PyInstanceObject 同样也有个类似的 instance_new 函数:

static PyObject *
instance_new(PyTypeObject* type, PyObject* args, PyObject *kw)
{

参数也和 class_new 类似,三个参数分别为 type , args 和 kw,

PyObject *klass;
PyObject *dict = Py_None;

if (!PyArg_ParseTuple(args, "O!|O:instance",
                      &PyClass_Type, &klass, &dict))
    return NULL;

解析参数,

if (dict == Py_None)
    dict = NULL;
else if (!PyDict_Check(dict)) {
    PyErr_SetString(PyExc_TypeError,
          "instance() second arg must be dictionary or None");
    return NULL;
}

检查 dict 参数的合法性,

    return PyInstance_NewRaw(klass, dict);
}

调用 PyInstance_NewRaw 函数,这个才是返回新实例对象的函数:

PyObject *
PyInstance_NewRaw(PyObject *klass, PyObject *dict)
{
    PyInstanceObject *inst;

参数只有所实例化自的类对象和属性方法字典 dict ,

if (!PyClass_Check(klass)) {
    PyErr_BadInternalCall();
    return NULL;
}
if (dict == NULL) {
    dict = PyDict_New();
    if (dict == NULL)
        return NULL;
}
else {
    if (!PyDict_Check(dict)) {
        PyErr_BadInternalCall();
        return NULL;
    }
    Py_INCREF(dict);
}

检查参数的合法性,如果 dict 为空 (NULL) 则调用 PyDict_New 参数新建一个字典对象赋给 dict,否则检查 dict 是否是一个 CPython 的字典对象,

inst = PyObject_GC_New(PyInstanceObject, &PyInstance_Type);
if (inst == NULL) {
    Py_DECREF(dict);
    return NULL;
}

同样是调用 PyObject_GC_New 函数,给这个新建的实例对象分配内存,PyInstance_Type 是一个全局的 PyTypeObject 类型的变量,

    inst->in_weakreflist = NULL;
    Py_INCREF(klass);
    inst->in_class = (PyClassObject *)klass;
    inst->in_dict = dict;
    _PyObject_GC_TRACK(inst);
    return (PyObject *)inst;
}

最后给新建的实例对象赋值相关属性,然后返回这个新建实例对象的指针。

对于 CPython 的实例对象而言,除了 instance_new 之外,还有另外的一个函数也可以创建一个实例对象:

PyObject *
PyInstance_New(PyObject *klass, PyObject *arg, PyObject *kw)
{
    register PyInstanceObject *inst;
    PyObject *init;
    static PyObject *initstr;

PyInstance_New 函数也有三个参数,除了第一个是 klass 表示类对象之外,另外两个和 instance_new 函数类似,

    if (initstr == NULL) {
        initstr = PyString_InternFromString("__init__");
        if (initstr == NULL)
            return NULL;
    }
    inst = (PyInstanceObject *) PyInstance_NewRaw(klass, NULL);

可以看到在这里调用了 PyInstance_NewRaw 函数创建一个新的实例对象,区别在于 dict 参数为 NULL ,这意味着新建的实例对象没有自己的属性和方法,

    if (inst == NULL)
        return NULL;
    init = instance_getattr2(inst, initstr);
    if (init == NULL) {
        if (PyErr_Occurred()) {
            Py_DECREF(inst);
            return NULL;
        }
        if ((arg != NULL && (!PyTuple_Check(arg) ||
                             PyTuple_Size(arg) != 0))
            || (kw != NULL && (!PyDict_Check(kw) ||
                              PyDict_Size(kw) != 0))) {
            PyErr_SetString(PyExc_TypeError,
                       "this constructor takes no arguments");
            Py_DECREF(inst);
            inst = NULL;
        }
    }

在新建的实例对象中查找初始化函数 init ,如果不存在 (init 为 NULL) 且发生错误,则返回 NULL ,没有错误则检查 arg 和 kw 这两个参数,设置错误字符串,同样将新建实例对象 inst 置为 NULL,

    else {
        PyObject *res = PyEval_CallObjectWithKeywords(init, arg, kw);
        Py_DECREF(init);
        if (res == NULL) {
            Py_DECREF(inst);
            inst = NULL;
        }
        else {
            if (res != Py_None) {
                PyErr_SetString(PyExc_TypeError,
                           "__init__() should return None");
                Py_DECREF(inst);
                inst = NULL;
            }
            Py_DECREF(res);
        }
    }

init 不为空即意味找到了初始化实例的函数,将初始化函数和参数 arg ,kw 作为参数调用,初始化这个实例对象,

    return (PyObject *)inst;
}

最后返回这个新建的实例对象。

查找函数与 getattr, setattr 函数

分析完创建类对象和实例对象的函数之后,我们来分析相关的查找函数,然后还有最重要的 getattr 和 setattr。类对象和实例对象都有自己特有的 getattr 和 setattr 函数,这两类函数正是 Python 中使用 dot 操作符取对象的属性值或者给对象属性赋值所调用的函数。

类对象的查找函数

首先是类对象的查找函数 class_lookup,在类对象的创建函数中也曾调用这个函数:

static PyObject *
class_lookup(PyClassObject *cp, PyObject *name, PyClassObject **pclass)
{

class_lookup 函数有三个参数,分别是类对象指针 cp,查找的属性名称 name 和指向类对象指针的指针变量 pclass,

    Py_ssize_t i, n;
    PyObject *value = PyDict_GetItem(cp->cl_dict, name);
    if (value != NULL) {
        *pclass = cp;
        return value;
    }

首先查找的是类对象 cp 的 cl_dict 字典,如果找到的值 value 不为空,即已经找到了这个属性的值,则将 pclass 所指向的地址为 cp 类对象的地址,然后返回这个 value,

    n = PyTuple_Size(cp->cl_bases);

否则计算类对象 cp 的父类的个数,也就是 cl_bases 元组的大小,

    for (i = 0; i < n; i++) {
        /* XXX What if one of the bases is not a class? */
        PyObject *v = class_lookup(
            (PyClassObject *)
            PyTuple_GetItem(cp->cl_bases, i), name, pclass);
        if (v != NULL)
            return v;
    }

对 cp 的所有父类递归调用 class_lookup 函数,直到找到这个 name 属性的值,返回到 v 变量,如果 v 非 NULL 则返回 v,

    return NULL;
}

否则返回 NULL ,表示查找不到这个 name 属性的值。

类对象的 getattr 函数

类对象的 getattr 函数实际上调用了 class_lookup函数,如下:

static PyObject *
class_getattr(register PyClassObject *op, PyObject *name)
{
    register PyObject *v;
    register char *sname;
    PyClassObject *klass;
    descrgetfunc f;

有两个参数,分别为类对象指针 op 和 所要获取的属性名称 name,

    if (!PyString_Check(name)) {
        PyErr_SetString(PyExc_TypeError, "attribute name must be a string");
        return NULL;
    }

首先也是检查参数的合法性,确定 name 为 PyString 对象,以防错误,

    sname = PyString_AsString(name);
    if (sname[0] == '_' && sname[1] == '_') {
        if (strcmp(sname, "__dict__") == 0) {
            if (PyEval_GetRestricted()) {
                PyErr_SetString(PyExc_RuntimeError,
               "class.__dict__ not accessible in restricted mode");
                return NULL;
            }
            Py_INCREF(op->cl_dict);
            return op->cl_dict;
        }
        if (strcmp(sname, "__bases__") == 0) {
            Py_INCREF(op->cl_bases);
            return op->cl_bases;
        }
        if (strcmp(sname, "__name__") == 0) {
            if (op->cl_name == NULL)
                v = Py_None;
            else
                v = op->cl_name;
            Py_INCREF(v);
            return v;
        }
    }

这一段首先是检查要获取的是否为特殊属性 __dict__, __bases____name__,如果是则返回这个类对象的那个特殊属性。之所以作这样的检查是因为接下来就要执行 class_lookup 函数查找,从上面的分析可以知道, class_lookup 函数还会查找其父类,而这些特殊属性每个类对象都有的,所以先做检查可以防止返回错误的属性值,

    v = class_lookup(op, name, &klass);
    if (v == NULL) {
        PyErr_Format(PyExc_AttributeError,
                     "class %.50s has no attribute '%.400s'",
                     PyString_AS_STRING(op->cl_name), sname);
        return NULL;
    }

通过 class_lookup 函数查找这个值,如果找不到则返回 NULL,

    f = TP_DESCR_GET(v->ob_type);
    if (f == NULL)
        Py_INCREF(v);
    else
        v = f(v, (PyObject *)NULL, (PyObject *)op);

    return v;
}

如果找到则尝试获取这个属性值对象的描述符,如果找到(实现了 __get__ 方法),则调用这个描述符方法,因为是类对象,所以第二个参数为 NULL。最后返回值 v 。

类对象的 setattr 函数

接下来的是类对象的 setattr 函数:

static int
class_setattr(PyClassObject *op, PyObject *name, PyObject *v)
{

class_setattr 函数有三个参数,分别是类对象指针 op,属性名称 name 和属性的值 v,

    char *sname;
    if (PyEval_GetRestricted()) {
        PyErr_SetString(PyExc_RuntimeError,
                   "classes are read-only in restricted mode");
        return -1;
    }

注意到这里首先检查了此时是否处于受限制模式,如果处于受限制模式,此时类对象是只读的,函数将返回错误码 -1。受限模式下,不受信任的代码的执行将会受到限制。

    if (!PyString_Check(name)) {
        PyErr_SetString(PyExc_TypeError, "attribute name must be a string");
        return -1;
    }
    sname = PyString_AsString(name);

然后是同样检查 name 参数是否为一个 PyString 对象,是则根据这个字符串对象返回一个 C 中的字符串,方便下面的比较。

接下来的一大段代码都是检查上面得到的这个 sname 字符串是否为特殊方法或者特殊的属性,比如 __dict__ 或者 __getattr__ 等,如果是则调用相关的函数 set_dict 等,一般来说这些特殊属性是不可以修改的,所以会返回错误提示。

    if (v == NULL) {
        int rv = PyDict_DelItem(op->cl_dict, name);
        if (rv < 0)
            PyErr_Format(PyExc_AttributeError,
                         "class %.50s has no attribute '%.400s'",
                         PyString_AS_STRING(op->cl_name), sname);
        return rv;
    }

参数 v 为空则将这个保存在类对象结构体 cl_dict 成员中的 name 属性删除掉,

    else
        return PyDict_SetItem(op->cl_dict, name, v);
}

否则,给这个属性 name 赋值 v,保存在类对象的 cl_dict 中。PyDict_SetItem 函数将会检测第一个字典参数中是否具有第二个参数 name 这个键,存在则更新其对应的值为 v,不存在则新建一个键,其值也是 v。

实例对象的 getattr 函数

实例对象只有一个简单地搜索属性字典 dict 的函数 _PyInstance_Lookup,这个函数很简单,就是里面做了一点的检查,然后就调用了 PyDict_GetItem 函数从实例对象的 dict 中获取这个值。

而实例对象的 getattr 函数则更多地调用到了class_lookup 函数。CPython 的源码中,关于实例对象的 getattr 和 setattr 函数灰常蛋疼,getattr 函数有三个,分别是 instance_getattrinstance_getattr1instance_getattr2...而 setattr 函数也有两个,分别是 instance_setattr1instance_setattr。如下:

static PyObject *
instance_getattr(register PyInstanceObject *inst, PyObject *name)
{

参数是实例对象指针 inst 和属性名称 name,

    register PyObject *func, *res;
    res = instance_getattr1(inst, name);

其实在这里就调用 instance_getattr1 函数了,参数是一致的,如果 instance_getattr1 函数的返回非 NULL,则直接会返回这个结果,下面一段不会执行,

    if (res == NULL && (func = inst->in_class->cl_getattr) != NULL) {
        PyObject *args;
        if (!PyErr_ExceptionMatches(PyExc_AttributeError))
            return NULL;
        PyErr_Clear();
        args = PyTuple_Pack(2, inst, name);
        if (args == NULL)
            return NULL;
        res = PyEval_CallObject(func, args);
        Py_DECREF(args);
    }

如果 isntance_getattr1 函数的返回值为 NULL 并且实例对象的类的 getattr 函数存在,则调用这个类对像的 getattr 函数,参数是将实例对象指针 inst 和属性名称 name 打包成的元组。

    return res;
}

最后返回结果。

instance_getattr1 函数如下:

static PyObject *
instance_getattr1(register PyInstanceObject *inst, PyObject *name)
{

参数同样是 inst 和 name,

    register PyObject *v;
    register char *sname;

    if (!PyString_Check(name)) {
        PyErr_SetString(PyExc_TypeError, "attribute name must be a string");
        return NULL;
    }

    sname = PyString_AsString(name);

例行检查参数的合法性,合法则将 name 参数转化为 C 的字符串,

    if (sname[0] == '_' && sname[1] == '_') {
        if (strcmp(sname, "__dict__") == 0) {
            if (PyEval_GetRestricted()) {
                PyErr_SetString(PyExc_RuntimeError,
            "instance.__dict__ not accessible in restricted mode");
                return NULL;
            }
            Py_INCREF(inst->in_dict);
            return inst->in_dict;
        }
        if (strcmp(sname, "__class__") == 0) {
            Py_INCREF(inst->in_class);
            return (PyObject *)inst->in_class;
        }
    }

同样是检查是否为特殊的属性,主要是以 __ 作为开头的属性,这里处理的只有 __dict____class__。如果是 __dict__ ,在受限模式下,会抛出错误表明不可以读取,非受限模式下则返回这个实例对象的属性字典 dict。如果是 __class__ ,也是对应地返回实例对象的类。

    v = instance_getattr2(inst, name);
    if (v == NULL && !PyErr_Occurred()) {
        PyErr_Format(PyExc_AttributeError,
                     "%.50s instance has no attribute '%.400s'",
                     PyString_AS_STRING(inst->in_class->cl_name), sname);
    }
    return v;
}

然后调用了 instance_getattr2 函数,如果其返回值为 NULL 则表示不存在这个属性,输出提示,否则返回这个结果 v。

static PyObject *
instance_getattr2(register PyInstanceObject *inst, PyObject *name)
{
    register PyObject *v;
    PyClassObject *klass;
    descrgetfunc f;

同样的, instance_getattr2 函数也是有两个参数 inst 和 name,

    v = PyDict_GetItem(inst->in_dict, name);
    if (v != NULL) {
        Py_INCREF(v);
        return v;
    }

首先在这个实例对象的 in_dict 中查找这个属性,如果找到则直接返回其值,

    v = class_lookup(inst->in_class, name, &klass);

没有找到则去查找这个实例对象的类对象 in_class,通过上面对 class_lookup 函数的分析我们可以知道,这个查找会一直从实例对象所属的类,其类的父类,父类的父类一直搜索,直到搜索完毕。如果找到了,则返回这个属性的值对象。

    if (v != NULL) {
        Py_INCREF(v);
        f = TP_DESCR_GET(v->ob_type);
        if (f != NULL) {
            PyObject *w = f(v, (PyObject *)inst,
                            (PyObject *)(inst->in_class));
            Py_DECREF(v);
            v = w;
        }

在这里同样也试图获取这个实例对象对应类型的描述符方法,

    }
    return v;
}

返回结果 v ,有值或者 NULL。

从对上面三个 getattr 函数的分析可以看到,其实这三个函数各有其功能,比如 instance_getattr1 处理的是特殊属性,而 instance_getattr2 则是对应普通的属性,会一直搜索到其所属的类和其类的父类等等。如果这两个函数都没有结果,则会调用其类的 getattr 函数。

所以这三个函数其实是有其各自的职责的,当然它们三个是可以合并起来成为一个大函数的,但是估计就是不希望看到一个大函数的出现所以才分散为三个函数,这样职责更小更分明。

实例对象的 setattr 函数

static int
instance_setattr(PyInstanceObject *inst, PyObject *name, PyObject *v)
{
    PyObject *func, *args, *res, *tmp;
    char *sname;

instance_setattr 函数有三个参数,毫无疑问分别是实例对象指针 inst ,属性名称 name 和值 v,

    if (!PyString_Check(name)) {
        PyErr_SetString(PyExc_TypeError, "attribute name must be a string");
        return -1;
    }

    sname = PyString_AsString(name);

同样,惯例检查 name 参数的合法性,合法则转化为 C 的字符串类型变量,

    if (sname[0] == '_' && sname[1] == '_') {
        Py_ssize_t n = PyString_Size(name);
        if (sname[n-1] == '_' && sname[n-2] == '_') {

判断是否为特殊属性,

            if (strcmp(sname, "__dict__") == 0) {
                if (PyEval_GetRestricted()) {
                    PyErr_SetString(PyExc_RuntimeError,
                 "__dict__ not accessible in restricted mode");
                    return -1;
                }
                if (v == NULL || !PyDict_Check(v)) {
                    PyErr_SetString(PyExc_TypeError,
                       "__dict__ must be set to a dictionary");
                    return -1;
                }
                tmp = inst->in_dict;
                Py_INCREF(v);
                inst->in_dict = v;
                Py_DECREF(tmp);
                return 0;
            }

__dict__ 则检查是否为受限模式,检查传入的 v 参数是否为合法的 PyDict 对象,如果是则将 v 赋值给实例对象的 in_dict。可以注意到,这里用了一个 tmp 变量来保存实例对象之前的 in_dict 变量,然后将其引用计数减一。

if (strcmp(sname, "__class__") == 0) {
    if (PyEval_GetRestricted()) {
        PyErr_SetString(PyExc_RuntimeError,
    "__class__ not accessible in restricted mode");
        return -1;
    }
    if (v == NULL || !PyClass_Check(v)) {
        PyErr_SetString(PyExc_TypeError,
           "__class__ must be set to a class");
        return -1;
    }
    tmp = (PyObject *)(inst->in_class);
    Py_INCREF(v);
    inst->in_class = (PyClassObject *)v;
    Py_DECREF(tmp);
    return 0;
}

如果是 __class__ 和上面的操作类似。通过这一段代码,我们可以看到在非受限模式的情况下,一个实例对象的类是可以被动态修改的。

        }
    }
    if (v == NULL)
        func = inst->in_class->cl_delattr;
    else
        func = inst->in_class->cl_setattr;

如果参数 v 为 NULL,则表示要将实例对象的这个属性删除掉,试图去获取实例对象所对应的类对象的 delattr 函数。v 不为 NULL 则获取类对象的 setattr 函数,

    if (func == NULL)
        return instance_setattr1(inst, name, v);

如果没有获取到任何的函数,则将会调用 instance_setattr1 函数。

    if (v == NULL)
        args = PyTuple_Pack(2, inst, name);
    else
        args = PyTuple_Pack(3, inst, name, v);
    if (args == NULL)
        return -1;
    res = PyEval_CallObject(func, args);

无论得到的是类对象的 delattr 还是 setattr 函数,这里将会调用这个函数,区别在于调用 delattr 函数参数元组只有 inst 和 name 而调用 setattr 函数参数则是多了一个参数 v。根据上面对类对象的 setattr 的分析可以知道,如果这个类有 setattr 函数,则将会调用它的 setattr 函数。

    Py_DECREF(args);
    if (res == NULL)
        return -1;
    Py_DECREF(res);
    return 0;
}

执行成功则返回 0。

static int
instance_setattr1(PyInstanceObject *inst, PyObject *name, PyObject *v)
{
    if (v == NULL) {
        int rv = PyDict_DelItem(inst->in_dict, name);
        if (rv < 0)
            PyErr_Format(PyExc_AttributeError,
                         "%.50s instance has no attribute '%.400s'",
                         PyString_AS_STRING(inst->in_class->cl_name),
                         PyString_AS_STRING(name));
        return rv;
    }

如果参数 v 为空,则表示删除这个属性,所以将会调用 PyDict_DelItem 函数将这个属性从实例对象的 dict 字典中删除,

    else
        return PyDict_SetItem(inst->in_dict, name, v);
}

否则就直接调用 PyDict_SetItem 函数更新 dict 中的这个值或者添加进 dict 中。

从上面对这两个 setattr 函数的分析,同样可以知道,这两个函数各自有其职责。instance_setattr 主要是对特殊属性进行处理或者是调用其类对象的 setattr 或者 delattr 函数,而 instance_setattr1 函数则是对这个实例对象的 dict 进行 set_item 或者 del_item 操作。

总结

其实写到后面已经有点头大了,引用了一大堆源码更像是给源码注释了。但是既然已经写了,那就当给源码注释把它给写完了。

虽然是罗嗦了一堆,但是通过这个分析过程,对于文章开头的那几段代码的情况还是很清晰的:

  • 首先,给实例对象 t 添加一个属性 ins_new_var 则将会保存到 t 的 __dict__ 中;
  • 而当试图在类对象 TestCls 中取 ins_new_var 的时候,只会去搜索这个类对象的 dict 和其父类的 dict ,这肯定是找不到的,所以返回属性错误;
  • 当给类对象 TestCls 添加一个属性 new_var 的时候,同样,会在 __dict__ 中添加一个 new_var 对象;
  • 当访问 t.new_var 的时候,在 t 的命名空间中搜索不到 new_var 的时候,就回去搜索其实例化自的类对象的命名空间,所以,就可以得到这个值了。

斯坦福大学公开课-编程范式 笔记 1

Posted on 日 07 七月 2013 in opencourse • Tagged with opencourse, programming-paradigms, notes • 2 min read

前言

最近在跟一门斯坦福大学的公开课 Programming Paradigms ,网易公开课也有其中文翻译版,翻译已经完成了:斯坦福大学公开课:编程范式

课程内容:

Advanced memory management features of C and C++; the differences between imperative and object-oriented paradigms. The functional paradigm (using LISP) and concurrent programming (using C and C++). Brief survey of other modern languages such as Python, Objective C, and C#.

首先涉及的是 C/C++ 的高级内存管理,内容包括 C 的各种数据类型的内存布局,malloc 和 free 的实现,等等。然后还有命令式和面向对象,函数式编程等等几种不同的编程范式及他们的差别。

可以说这些内容应该是属于比较高级的内容。我之前偶尔也会接触到一些,有些书也会讲到,但是在我所在的大学的课堂上,这些内容基本上是不讲授的。在上 C 课程的时候,甚至连指针都语焉不详,更别提有一门专门的课程来讲述这些高级内容。上这个公开课刚好可以完整地补全我对这方面内容的不足,毕竟一个斯坦福大学的教授给你讲解这些内容总比自己看书效果要好得多。

在这里,我要记下的是在课程中碰到的一些有趣的内容。

malloc 与 free

我们知道,malloc 函数是 C 中动态分配内存的一个函数,通过这个函数可以在堆中申请一块限定大小的内存使用。对应地,有申请内存函数就有释放内存函数,这个函数就是 free 函数。这两个函数的原型如下:

#include <stdlib.h>

void *malloc(size_t size);
void free(void *ptr);

通过这两个函数的原型我们可以看到一些信息。malloc 函数的参数是一个 size_t 类型的变量 size ,而 malloc 返回的则是一个 void * 类型的指针。这不意外,因为我们知道通过 malloc 函数分配制定大小的内存成功之后,会将这块内存的首地址作为返回值返回给某个类型的指针变量。而通过 C 的自动类型转换,void * 类型的指针地址将会被转换为该指针变量类型的指针。

free 函数也是只有一个参数,类型为 void * 的指针变量 ptr ,无返回值。在这里问题出现了, free 函数如果只是接受某块内存的首地址作为参数,那它是如何得知这块内存的大小?或者说,free 函数怎么知道需要被释放的内存到底有多大?这块内存的大小是必要要知道的,因为如果不知道,free 函数是无法准确地将要释放的内存释放掉,也许会将后面接着的不允许释放的内存也释放掉,也许还遗留一部分内存没有释放掉。

所以,编译器,或者操作系统,肯定是提供了一种机制来告知 free 函数,这块在堆中的内存的大小。那这个机制是什么呢?

malloc 的机制

答案并不复杂,那就是在 malloc 函数返回的地址前面,有 4 字节或者 8 字节同样也是属于这块内存的内容,这几个字节中储存了该内存地址的大小。free 函数接受这个地址的时候,会回退一部分地址,根据这个结构的内容,得到该内存块的大小,然后将相关的释放掉。

以上是公开课中老师的简单讲述,那实际情况是怎样?下面进行一下简单的验证。首先测试的平台是 Fedora17 ,Linux 的内核版本为 3.6.11-5.fc17.i686.PAE ,使用的 C 编译器为 GCC 4.7.2 20120921 (Red Hat 4.7.2-2) 。测试代码如下:

#include <stdio.h>
#include <stdlib.h>


//Test the memory alloc by malloc.
int main(int argc, char *argv[])
{
    int *ptr1, *ptr2, num1, num2;
    char *cptr;

    ptr1 = &num1;
    ptr1 = (int *)malloc(512 * sizeof(int));
    ptr2 = &num2;
    ptr2 = (int *)malloc(1024 * sizeof(int));
    cptr = (char *)malloc(1024 * sizeof(char));


    printf("The start of num1 memory address is %x.\n", ptr1);
    printf("Before this address, the value is %d.\n", *(ptr1 - 1));
    printf("The start of num2 memory address is %x.\n", ptr2);
    printf("Before this address, the value is %d.\n", *(ptr2 - 1));
    printf("The start of char memory address is %x.\n", cptr);
    printf("Before this address, the value is %d.\n", *((int *)cptr - 1));
}

测试代码并不复杂,简单定义了两个 int 类型的指针变量和一个 char * 类型的指针变量,然后用 malloc 函数分配一定数量的内存,返回的内存块首地址赋值给这三个变量,然后输出这三个内存块首地址前一个位置的值。注意对于 char * 类型的地址,首先强制转换成 int * 类型的再进行 -1 操作。

代码的输出结果如下所示:

╰ ➤ ./a.out 
The start of num1 memory address is 9922008.
Before this address, the value is 2057.
The start of num2 memory address is 9922810.
Before this address, the value is 4105.
The start of char memory address is 9923818.
Before this address, the value is 1033.

通过代码我们可以知道,ptr1 申请的内存块大小为 512 * 4 = 2048,ptr2 申请的内存块大小为 1024 * 4 = 4096,cptr 申请的内存块大小为 1024 * 1 = 1024,以上单位均为字节。根据输出,有如下计算:

2057 - 2048 = 9
4105 - 4096 = 9
1033 - 1024 = 9

可见,如果 malloc 函数返回的地址前一个字节保存了该内存块的整体大小,那可以推测到,其中 9 个字节作为额外的结构,保存了这块内存的信息,以提供给其他函数比如 free 函数利用。当然这只是一个推测,真实的情况需要深入到 glibc 的库的 malloc 和 free 的源码中。

实际上,老师也说了,不同的编译器的实现是不同的,比如,可以参考下这篇文章:malloc/new函数及malloc()的一种简单原理性实现

参考: