您当前的位置:首页 > 计算机 > 编程开发 > Python

Python 源码剖析 - Small Python

时间:12-14来源:作者:点击数:
城东书院 www.cdsy.xyz

1. Small Python

在详细考察了 Python 中最常用的几个对象之后,我们现在完全可以利用这些对象做出一个最简单的 Python。这一章的目的就是模拟出一个最简单的Python——Small Python。

在 Small Python 中,我们首先需要实现之前已经分析过的那些对象,比如 PyIntObject,与 CPython 不同的是,我们并没有实现象CPython那样复杂的机制,作为一个模拟程序,我们只实现了简单的功能,也没有引入对象缓冲池的机制。这一切都是为了简洁而清晰地展示出Python运行时的脉络。

在 Small Python 中,实际上还需要实现 Python 的运行时环境,Python 的运行时环境是我们在以后的章节中将要剖析的重点。在这里只是展示了其核心的思想——利用 PyDictObject 对象来维护变量名到变量值的映射。

当然,在 CPython 中,还有许多其他的主题,比如Python源代码的编译,Python 字节码的生成和执行等等,在Small Python中,我们都不会涉及,因为到目前为止,我们没有任何资本做出如此逼真的模拟。不过当我们完成这本书的探索之后,就完全有能力实现一个真正的Python了。

在 Small Python 中,我们仅仅实现了 PyIntObject,PyStringObject 以及 PyDictObject 对象,仅仅实现了加法运算和输出操作。同时编译的过程也被简化到了极致,因此我们的 Small Python 只能处理非常受限的表达式。虽然很简陋,但从中可以看到 Python 的骨架,同时,这也是我们深入Python解释器和运行时的起点。

2. 对象机制

在 Small Python 中,对象机制与 CPython 完全相同:

[PyObject] 
#define PyObject_HEAD \ 
    int refCount;\ 
    struct tagPyTypeObject *type 
#define PyObject_HEAD_INIT(typePtr)\ 
    0, typePtr 
typedef struct tagPyObject{ 
    PyObject_HEAD; 
}PyObject;

但是对于类型对象,我们进行了大规模的删减。最终在类型对象中,只定义了加法操作,Hash 操作以及输出操作:

[PyTypeObject] 
//definition of PyTypeObject 
typedef void (*PrintFun)(PyObject* object); 
typedef PyObject* (*AddFun)(PyObject* left, PyObject* right); 
typedef long (*HashFun)(PyObject* object);
typedef struct tagPyTypeObject{ 
    PyObject_HEAD; 
    char* name; 
    PrintFun print; 
    AddFun add; 
    HashFun hash; 
}PyTypeObject;

PyIntObject 的实现与 CPython 几乎是一样的,不过没有复杂的对象缓冲机制:

[PyIntObject] 
typedef struct tagPyIntObject 
{ 
    PyObject_HEAD; 
    int value; 
}PyIntObject; 
PyObject* PyInt_Create(int value) 
{ 
    PyIntObject* object = new PyIntObject; 
    object->refCount = 1; 
    object->type = &PyInt_Type; 
    object->value = value; 
    return (PyObject*)object; 
} 
static void int_print(PyObject* object) 
{ 
    PyIntObject* intObject = (PyIntObject*)object; 
    printf("%d\n", intObject->value); 
} 
static PyObject* int_add(PyObject* left, PyObject* right) 
{ 
    PyIntObject* leftInt = (PyIntObject*)left; 
    PyIntObject* rightInt = (PyIntObject*)right; 
    PyIntObject* result = (PyIntObject*)PyInt_Create(0); 
    if(result == NULL) 
    { 
        printf("We have no enough memory!!"); 
    } 
    else 
    { 
        result->value = leftInt->value + rightInt->value; 
    } 
    return (PyObject*)result; 
} 
static long int_hash(PyObject* object) 
{ 
    return (long)((PyIntObject*)object)->value; 
} 
PyTypeObject PyInt_Type = 
{ 
    PyObject_HEAD_INIT(&PyType_Type), 
    "int", 
    int_print, 
    int_add, 
    int_hash 
};

Small Python 中的 PyStringObject 与 CPython 中大不相同,在 CPython 中,它是一个变长对象,而 Small Python 中只是一个简单的定长对象,因为 Small Python 的定位就是个演示的程序:

[PyStrObject] 
typedef struct tagPyStrObject 
{ 
    PyObject_HEAD; 
    int length; 
    long hashValue; 
    char value[50]; 
}PyStringObject; 
PyObject* PyStr_Create(const char* value) 
{ 
    PyStringObject* object = new PyStringObject; 
    object->refCount = 1; 
    object->type = &PyString_Type; 
    object->length = (value == NULL) ? 0 : strlen(value); 
    object->hashValue = -1; 
    memset(object->value, 0, 50); 
    if(value != NULL) 
    { 
        strcpy(object->value, value); 
    } 
    return (PyObject*)object; 
} 
static void string_print(PyObject* object) 
{ 
    PyStringObject* strObject = (PyStringObject*)object; 
    printf("%s\n", strObject->value); 
} 
static long string_hash(PyObject* object) 
{ 
    PyStringObject* strObject = (PyStringObject*)object; 
    register int len; 
    register unsigned char *p; 
    register long x; 
    if (strObject->hashValue != -1) 
        return strObject->hashValue; 
    len = strObject->length; 
    p = (unsigned char *)strObject->value; 
    x = *p << 7; 
    while (--len >= 0) 
        x = (1000003*x) ^ *p++; 
    x ^= strObject->length; 
    if (x == -1) 
        x = -2; 
    strObject->hashValue = x; 
    return x; 
} 
static PyObject* string_add(PyObject* left, PyObject* right) 
{ 
    PyStringObject* leftStr = (PyStringObject*)left; 
    PyStringObject* rightStr = (PyStringObject*)right; 
    PyStringObject* result = (PyStringObject*)PyStr_Create(NULL); 
    if(result == NULL) 
    { 
        printf("We have no enough memory!!"); 
    } 
    else 
    { 
        strcpy(result->value, leftStr->value); 
        strcat(result->value, rightStr->value); 
    } 
    return (PyObject*)result; 
} 
PyTypeObject PyString_Type = 
{ 
    PyObject_HEAD_INIT(&PyType_Type), 
        "str", 
        string_print, 
        string_add, 
        string_hash 
};

在 Python 的解释器工作时,还有一个非常重要的对象,PyDictObject 对象。PyDictObject 对象在 Python 运行时会维护(变量名,变量值)的映射关系,Python 所有的动作都是基于这种映射关系。在 Small Python 中,我们基于 C++ 中的 map 来实现 PyDictObject 对象。当然,map 的运行效率比 CPython 中所采用的 hash 技术会慢上一些,但是对于我们的 Small Python,map 就足够了:

[PyDictObject] 
typedef struct tagPyDictObject 
{ 
    PyObject_HEAD; 
    map<long, PyObject*> dict; 
}PyDictObject; 
PyObject* PyDict_Create() 
{ 
    //create object 
    PyDictObject* object = new PyDictObject; 
    object->refCount = 1; 
    object->type = &PyDict_Type; 
    return (PyObject*)object; 
} 
PyObject* PyDict_GetItem(PyObject* target, PyObject* key) 
{ 
    long keyHashValue = (key->type)->hash(key); 
    map<long, PyObject*>& dict = ((PyDictObject*)target)->dict; 
    map<long, PyObject*>::iterator it = dict.find(keyHashValue); 
    map<long, PyObject*>::iterator end = dict.end(); 
    if(it == end) 
    { 
        return NULL; 
    } 
    return it->second; 
} 
int PyDict_SetItem(PyObject* target, PyObject* key, PyObject* value) 
{ 
    long keyHashValue = (key->type)->hash(key); 
    PyDictObject* dictObject = (PyDictObject*)target; 
    (dictObject->dict)[keyHashValue] = value; 
    return 0; 
} 
// function for PyDict_Type 
static void dict_print(PyObject* object) 
{ 
    PyDictObject* dictObject = (PyDictObject*)object; 
    printf("{"); 
    map<long, PyObject*>::iterator it = (dictObject->dict).begin(); 
    map<long, PyObject*>::iterator end = (dictObject->dict).end(); 
    for( ; it != end; ++it) 
    { 
        //print key 
        printf("%d : ", it->first); 
        //print value 
        PyObject* value = it->second; 
        (value->type)->print(value); 
        printf(", "); 
    } 
    printf("}\n"); 
} 
PyTypeObject PyDict_Type = 
{ 
    PyObject_HEAD_INIT(&PyType_Type), 
        "dict", 
        dict_print, 
        0, 
        0 
};

Small Python 中的对象机制的所有内容都在上边列出了,非常简单,对吧,这就对了,要的就是这个简单。

3. 解释过程

说 Small Python 中没有编译,对的,它根本就不会进行任何常规的编译动作,没有 token 解析,没有抽象语法树的建立。但说 Small Python 中有那么一点点编译的味道,其实也不错,我们叫这种动作为解释。无论如何,它至少要解析输入的语句,以判断这条语句到底是要干什么,它是要上山打虎呢,还是要下河摸鱼呢,如果连这最基本的都做不到,Small Python 还不如回家卖红薯得了。

然而 Small Python 中的这种解释动作还是被简化到了极致,它实际上就是简单的字符串查找加 if…else… 结构:

void ExcuteCommand(string& command)
{ 
  string::size_type pos = 0; 
  if((pos = command.find("print ")) != string::npos) 
  { 
      ExcutePrint(command.substr(6)); 
  } 
  else if((pos = command.find(" = ")) != string::npos) 
  { 
      string target = command.substr(0, pos); 
      string source = command.substr(pos+3); 
      ExcuteAdd(target, source); 
  } 
} 
void ExcuteAdd(string& target, string& source)
{ 
  string::size_type pos; 
  if(IsSourceAllDigit(source)) 
  { 
      PyObject* intValue = PyInt_Create(atoi(source.c_str())); 
      PyObject* key = PyStr_Create(target.c_str()); 
      PyDict_SetItem(m_LocalEnvironment, key, intValue); 
  } 
  else if(source.find("\"") != string::npos) 
  { 
      PyObject* strValue = PyStr_Create(source.substr(1, source.size()-2).c_str()); 
      PyObject* key = PyStr_Create(target.c_str()); 
      PyDict_SetItem(m_LocalEnvironment, key, strValue); 
  } 
  else if((pos = source.find("+")) != string::npos) 
  { 
      PyObject* leftObject = GetObjectBySymbol(source.substr(0, pos)); 
      PyObject* rightObject = GetObjectBySymbol(source.substr(pos+1)); 
      if(leftObject != NULL && right != NULL && leftObject->type == rightObject->type) 
      { 
          PyObject* resultValue = (leftObject->type)->add(leftObject, rightObject); 
                PyObject* key = PyStr_Create(target.c_str()); 
          PyDict_SetItem(m_LocalEnvironment, key, resultValue); 
      } 
      (m_LocalEnvironment->type)->print(m_LocalEnvironment); 
  } 
}

通过字符串搜索,如果命令中出现 =,就是一个赋值或加法过程;如果命令中出现 print,就是一个输出过程。进一步,在 ExcuteAdd 中,又进一步进行字符串搜索,以确定是否需要有一个额外的加法过程。根据这些解析的结果进行不同的动作,就是 Small Python 中的解释过程。这个过程在 CPython 中是通过正常的编译过程来实现的,而且最后会得到字节码的编译结果。

在这里需要重点指出的是那个 m_LocalEnvironment,这是一个 PyDictObject 对象,其中维护着 Small Python 运行过程中,动态创建的变量的变量名和变量值的映射。这个就是 Small Python 中的执行环境,Small Python 正是靠它来维护运行过程中的所有变量的状态。在 CPython中,运行环境实际上也是这样一个机制。当需要访问变量时,就从这个 PyDictObject 对象中查找变量的值。这一点在执行输出操作时可以看得很清楚:

PyObject* GetObjectBySymbol(string& symbol)
{ 
    PyObject* key = PyStr_Create(symbol.c_str()); 
    PyObject* value = PyDict_GetItem(m_LocalEnvironment, key); 
    if(value == NULL) 
    { 
        cout << "[Error] : " << symbol << " is not defined!!" << endl; 
        return NULL; 
    } 
    return value; 
} 
void ExcutePrint(string symbol) 
{ 
    PyObject* object = GetObjectFromSymbol(symbol); 
    if(object != NULL) 
    { 
        PyTypeObject* type = object->type; 
        type->print(object); 
    } 
}

在这里,通过变量名 symbol,获得了变量值 object。而在刚才的 ExcueteAdd 中,我们将变量名和变量值建立了联系,并存放到 m_LocalEnvironment 中。这种一进一出的机制正是 CPython 执行时的关键,在以后对 Python 字节码解释器的详细剖析中,我们将真实而具体地看到这种机制。

4. 交互式环境

好了,我们的 Small Python 几乎已经完成了,最后所缺的就是一个交互式环境:

char* info = "********** Python Research **********\n"; 
char* prompt = ">>> "; 
void Excute() 
{ 
    cout << info; 
    cout << prompt; 
    while(getline(cin, m_Command){ 
        if(m_Command.size() == 0){ 
            cout << prompt; 
            continue; 
        } 
        else if(m_Command == "exit"){ 
            return; 
        } 
        else{ 
            ExcuteCommand(m_Command); 
        } 
        cout << prompt; 
    } 
}

有了它,我们的 Small Python 就大功告成了。现在,来看一看我们的成果吧:

到这里,我们就结束了对 Python 中对象的探索,通过 Small Python 这一个简陋的承前启后的东西,我们将敲开 Python 运行时的大门,那里是字节码,解释器,条件判断语句,函数,类,异常等等的神秘世界,提起精神,我们出发了…

相关源码:

small_python.zip
bbc776f1e237e74d02e022ee51018f7c.zip (2.15 MB)
城东书院 www.cdsy.xyz
方便获取更多学习、工作、生活信息请关注本站微信公众号城东书院 微信服务号城东书院 微信订阅号
推荐内容
相关内容
栏目更新
栏目热门
本栏推荐