Python源码分析

本文环境python2.5系列
参考书籍<<Python源码剖析>>

上一文,分析了Python在启动初始化时,对内置类的一个基本的初始化流程,本文就简析一下用户自定义类的实现过程。

分析

脚本如下;

class A(object):name = 'attr_a'def __init__(self):print("A.__init__")def f(self):print("A.f")def g(self, aValue):self.aValue = aValueprint(self.aValue)print("A.g")   a = A()
a.f()
a.g(5)

根据该脚本代码可知,该脚本包含两段PyCodeObject,脚本对应的字节码如下;

  1           0 LOAD_CONST               0 ('A')3 LOAD_NAME                0 (object)6 BUILD_TUPLE              19 LOAD_CONST               1 (<code object A at 0x10894a530, file "test_class.py", line 1>)12 MAKE_FUNCTION            015 CALL_FUNCTION            018 BUILD_CLASS         19 STORE_NAME               1 (A)15          22 LOAD_NAME                1 (A)25 CALL_FUNCTION            028 STORE_NAME               2 (a)16          31 LOAD_NAME                2 (a)34 LOAD_ATTR                3 (f)37 CALL_FUNCTION            040 POP_TOP             17          41 LOAD_NAME                2 (a)44 LOAD_ATTR                4 (g)47 LOAD_CONST               2 (5)50 CALL_FUNCTION            153 POP_TOP             54 LOAD_CONST               3 (None)57 RETURN_VALUE  

其中类A对应的字节码如下;

  1           0 LOAD_GLOBAL              0 (__name__)3 STORE_NAME               1 (__module__)2           6 LOAD_CONST               1 ('attr_a')9 STORE_NAME               2 (name)4          12 LOAD_CONST               2 (<code object __init__ at 0x1020cab30, file "test_class.py", line 4>)15 MAKE_FUNCTION            018 STORE_NAME               3 (__init__)7          21 LOAD_CONST               3 (<code object f at 0x1020caab0, file "test_class.py", line 7>)24 MAKE_FUNCTION            027 STORE_NAME               4 (f)10          30 LOAD_CONST               4 (<code object g at 0x102111ab0, file "test_class.py", line 10>)33 MAKE_FUNCTION            036 STORE_NAME               5 (g)39 LOAD_LOCALS         40 RETURN_VALUE 

其中,类A中对应的三个函数对应的字节码如下;

Disassembly of __init__:4           0 LOAD_CONST               1 ('A.__init__')3 PRINT_ITEM          4 PRINT_NEWLINE       5 LOAD_CONST               0 (None)8 RETURN_VALUE        Disassembly of f:6           0 LOAD_CONST               1 ('A.f')3 PRINT_ITEM          4 PRINT_NEWLINE       5 LOAD_CONST               0 (None)8 RETURN_VALUE        Disassembly of g:8           0 LOAD_FAST                1 (aValue)3 LOAD_FAST                0 (self)6 STORE_ATTR               0 (aValue)9           9 LOAD_FAST                0 (self)12 LOAD_ATTR                0 (aValue)15 PRINT_ITEM          16 PRINT_NEWLINE       10          17 LOAD_CONST               1 ('A.g')20 PRINT_ITEM          21 PRINT_NEWLINE       22 LOAD_CONST               0 (None)25 RETURN_VALUE

首先先看下脚本执行的字节码文件,执行流程如下;

  1           0 LOAD_CONST               0 ('A')3 LOAD_NAME                0 (object)6 BUILD_TUPLE              19 LOAD_CONST               1 (<code object A at 0x10894a530, file "test_class.py", line 1>)

先将A加载到常量中,然后建立了一个元素大小的元组并将object放入其中,并将新建的元组压栈,然后将A对应的字节码压栈,执行完之后栈中的布局就变为如下所示;

|  A            |
| tuple(object) |
| code A        |

然后继续执行字节码;

             12 MAKE_FUNCTION            015 CALL_FUNCTION            018 BUILD_CLASS         19 STORE_NAME               1 (A)

此时,通过分析函数调用的过程可知,MAKE_FUNCTION将对应的code A包装成一个PyFunctionObject,然后调用CALL_FUNCTION执行对应的code A中的字节码,由此查看类A对应的字节码;

  1           0 LOAD_GLOBAL              0 (__name__)3 STORE_NAME               1 (__module__)2           6 LOAD_CONST               1 ('attr_a')9 STORE_NAME               2 (name)4          12 LOAD_CONST               2 (<code object __init__ at 0x1020cab30, file "test_class.py", line 4>)15 MAKE_FUNCTION            018 STORE_NAME               3 (__init__)7          21 LOAD_CONST               3 (<code object f at 0x1020caab0, file "test_class.py", line 7>)24 MAKE_FUNCTION            027 STORE_NAME               4 (f)10          30 LOAD_CONST               4 (<code object g at 0x102111ab0, file "test_class.py", line 10>)33 MAKE_FUNCTION            036 STORE_NAME               5 (g)39 LOAD_LOCALS         40 RETURN_VALUE 

此时,code A中对应的names列表如下;

('__name__', '__module__', 'name', '__init__', 'f', 'g')

先执行了LOAD_GLOBAL设置name,接着就将name存储到module中,然后加载常量name,然后加载函数init,加载f,加载g,此时每一次都通过STORE_NAME压入,

        case STORE_NAME:w = GETITEM(names, oparg);      // 从变量列表中获取对应的变量v = POP();if ((x = f->f_locals) != NULL) {if (PyDict_CheckExact(x))err = PyDict_SetItem(x, w, v);    // 检查locals是是否字典类型,如果是字典类型则将对应的name与结果设置到字典中elseerr = PyObject_SetItem(x, w, v);Py_DECREF(v);if (err == 0) continue;break;}PyErr_Format(PyExc_SystemError,"no locals found when storing %s",PyObject_REPR(w));break;

由此可知,在进过STORE_NAME之后,就将class A中对应的定义过的方法属性等信息保存了起来,当执行到最后两步时,

             39 LOAD_LOCALS         40 RETURN_VALUE

调用LOAD_LOCALS时,

        case LOAD_LOCALS:if ((x = f->f_locals) != NULL) {Py_INCREF(x);PUSH(x);continue;}PyErr_SetString(PyExc_SystemError, "no locals");break;

将帧对应的变量列表压栈,此时的x就是STORE_NAME中压入的x,然后将x作为值返回后,我们继续查看脚本文件的字节码执行,此时脚本字节码如下;

             18 BUILD_CLASS         19 STORE_NAME               1 (A)

此时我们查看BUILD_CLASS的代码;

        case BUILD_CLASS:u = TOP();                //栈中第三个位置的值,此时就是调用call_function后,生成的f-locals中的字典 v = SECOND();             // 栈中的第二个位置的值,此时对应一个包含object的元组w = THIRD();              // 压入的栈顶值,此时对应ASTACKADJ(-2);             // 将栈置顶x = build_class(u, v, w);  // 创建classSET_TOP(x);               // 将v设置到栈低一位的位置上Py_DECREF(u);Py_DECREF(v);Py_DECREF(w);break;

我们继续查看build_class的执行过程;

static PyObject *
build_class(PyObject *methods, PyObject *bases, PyObject *name)
{PyObject *metaclass = NULL, *result, *base;if (PyDict_Check(methods))metaclass = PyDict_GetItemString(methods, "__metaclass__");   // 如果用户定义了__metaclass__则使用用户自己定义的if (metaclass != NULL)Py_INCREF(metaclass);else if (PyTuple_Check(bases) && PyTuple_GET_SIZE(bases) > 0) {   // 检查bases是否是大于0大小的元组base = PyTuple_GET_ITEM(bases, 0);                            // 获取元组中索引值为0 的基类metaclass = PyObject_GetAttrString(base, "__class__");        // 获取base中的__class__属性if (metaclass == NULL) {                                      // 如果还是没有获取到元类PyErr_Clear();metaclass = (PyObject *)base->ob_type;                    // 则设置type为该类的元类Py_INCREF(metaclass);}}else {PyObject *g = PyEval_GetGlobals();                            // 获取全局的变量if (g != NULL && PyDict_Check(g))                           metaclass = PyDict_GetItemString(g, "__metaclass__");     // 如果全局变量字典中,获取元类if (metaclass == NULL)metaclass = (PyObject *) &PyClass_Type;                   // 如果任然没有获取到元类信息,则是classobj作为元类Py_INCREF(metaclass);}result = PyObject_CallFunctionObjArgs(metaclass, name, bases, methods, NULL);  // 调用该函数创建类Py_DECREF(metaclass);...return result;
}

此时就是先获取要创建类的元类,然后调用PyObject_CallFunctionObjArgs将参数传入生成类;继续查看PyObject_CallFunctionObjArgs执行过程;

PyObject *
PyObject_CallFunctionObjArgs(PyObject *callable, ...)
{PyObject *args, *tmp;va_list vargs;if (callable == NULL)return null_error();/* count the args */va_start(vargs, callable);    // 获取传入的变长参宿args = objargs_mktuple(vargs);  // 将变长参数转变成一个元组va_end(vargs);              if (args == NULL)return NULL;tmp = PyObject_Call(callable, args, NULL);  // 调用该函数Py_DECREF(args);return tmp;
}

继续查看PyObject_Call代码;

PyObject *
PyObject_Call(PyObject *func, PyObject *arg, PyObject *kw)
{ternaryfunc call;if ((call = func->ob_type->tp_call) != NULL) {         PyObject *result = (*call)(func, arg, kw);     // 此时调用func->ob_type的tp_call方法if (result == NULL && !PyErr_Occurred())PyErr_SetString(PyExc_SystemError,"NULL result without error in PyObject_Call");return result;}PyErr_Format(PyExc_TypeError, "'%.200s' object is not callable",func->ob_type->tp_name);return NULL;
}

此时由于传入的基类时object类,所以调用的是object类中的ob_type->tp_call,此时的ob_type为type,type的tp_call如下;

PyTypeObject PyType_Type = {PyObject_HEAD_INIT(&PyType_Type)0,                  /* ob_size */"type",                 /* tp_name */sizeof(PyHeapTypeObject),       /* tp_basicsize */sizeof(PyMemberDef),            /* tp_itemsize */(destructor)type_dealloc,       /* tp_dealloc */...(ternaryfunc)type_call,         /* tp_call */0,                  /* tp_str */(getattrofunc)type_getattro,        /* tp_getattro */(setattrofunc)type_setattro,        /* tp_setattro */...type_methods,               /* tp_methods */type_members,               /* tp_members */type_getsets,               /* tp_getset */...offsetof(PyTypeObject, tp_dict),    /* tp_dictoffset */...type_new,               /* tp_new */...
};

type_call,此时我们查看type_call中进行了什么操作;


static PyObject *
type_call(PyTypeObject *type, PyObject *args, PyObject *kwds)
{PyObject *obj;if (type->tp_new == NULL) {                          // 检查type的tp_new是否为空,为空则报错PyErr_Format(PyExc_TypeError,"cannot create '%.100s' instances",type->tp_name);return NULL;}obj = type->tp_new(type, args, kwds);           // 调用type的tp_new方法来新建objif (obj != NULL) {/* Ugly exception: when the call was type(something),don't call tp_init on the result. */if (type == &PyType_Type &&PyTuple_Check(args) && PyTuple_GET_SIZE(args) == 1 &&(kwds == NULL ||(PyDict_Check(kwds) && PyDict_Size(kwds) == 0)))return obj;/* If the returned object is not an instance of type,it won't be initialized. */if (!PyType_IsSubtype(obj->ob_type, type))return obj;type = obj->ob_type;if (PyType_HasFeature(type, Py_TPFLAGS_HAVE_CLASS) &&type->tp_init != NULL &&type->tp_init(obj, args, kwds) < 0) {       // 如果不是新建而是创建实例,还调用tp_init方法Py_DECREF(obj);obj = NULL;}}return obj;
}

此时,调用的是type的tp_new,由上一段代码片段可知,调用了PyType_Type中的type_new方法;

static PyObject *
type_new(PyTypeObject *metatype, PyObject *args, PyObject *kwds)
{PyObject *name, *bases, *dict;static char *kwlist[] = {"name", "bases", "dict", 0};PyObject *slots, *tmp, *newslots;PyTypeObject *type, *base, *tmptype, *winner;PyHeapTypeObject *et;PyMemberDef *mp;Py_ssize_t i, nbases, nslots, slotoffset, add_dict, add_weak;int j, may_add_dict, may_add_weak;assert(args != NULL && PyTuple_Check(args));assert(kwds == NULL || PyDict_Check(kwds));/* Special case: type(x) should return x->ob_type */{const Py_ssize_t nargs = PyTuple_GET_SIZE(args);const Py_ssize_t nkwds = kwds == NULL ? 0 : PyDict_Size(kwds);if (PyType_CheckExact(metatype) && nargs == 1 && nkwds == 0) {PyObject *x = PyTuple_GET_ITEM(args, 0);Py_INCREF(x->ob_type);return (PyObject *) x->ob_type;}/* SF bug 475327 -- if that didn't trigger, we need 3arguments. but PyArg_ParseTupleAndKeywords below may givea msg saying type() needs exactly 3. */if (nargs + nkwds != 3) {PyErr_SetString(PyExc_TypeError,"type() takes 1 or 3 arguments");return NULL;}}/* Check arguments: (name, bases, dict) */if (!PyArg_ParseTupleAndKeywords(args, kwds, "SO!O!:type", kwlist,&name,&PyTuple_Type, &bases,&PyDict_Type, &dict))    // 将传入的参数依次解析到 name bases dict三个变量中return NULL;/* Determine the proper metatype to deal with this,and check for metatype conflicts while we're at it.Note that if some other metatype wins to contract,it's possible that its instances are not types. */nbases = PyTuple_GET_SIZE(bases);         // 获取基类列表的大小winner = metatype;                        // 默认设置传入的为最佳元类for (i = 0; i < nbases; i++) {tmp = PyTuple_GET_ITEM(bases, i);     // 依次遍历基类列表tmptype = tmp->ob_type;               // 获取基类的类型if (tmptype == &PyClass_Type)         // 如果获取的基类是classobj则跳过continue; /* Special case classic classes */if (PyType_IsSubtype(winner, tmptype))      // 检查winner tmptype是否是子类,获取是父类选择最佳类型continue;if (PyType_IsSubtype(tmptype, winner)) {winner = tmptype;continue;}PyErr_SetString(PyExc_TypeError,"metaclass conflict: ""the metaclass of a derived class ""must be a (non-strict) subclass ""of the metaclasses of all its bases");return NULL;}if (winner != metatype) {                  // 如果比较之后的类不是传入的最初设置的类则将比较后的值设置到metatypeif (winner->tp_new != type_new) /* Pass it to the winner */return winner->tp_new(winner, args, kwds);   // 如果比较之后的winner的tp_new不是type_new方法,则调用设置后的方法metatype = winner;                   }/* Adjust for empty tuple bases */if (nbases == 0) {                       // 如果基类列表为空bases = PyTuple_Pack(1, &PyBaseObject_Type);  // 设置基类列表的基类为objectif (bases == NULL)return NULL;nbases = 1;}elsePy_INCREF(bases);/* XXX From here until type is allocated, "return NULL" leaks bases! *//* Calculate best base, and check that all bases are type objects */base = best_base(bases);                // 比较获取最佳的父类if (base == NULL) {Py_DECREF(bases);return NULL;}if (!PyType_HasFeature(base, Py_TPFLAGS_BASETYPE)) {PyErr_Format(PyExc_TypeError,"type '%.100s' is not an acceptable base type",base->tp_name);Py_DECREF(bases);return NULL;}/* Check for a __slots__ sequence variable in dict, and count it */slots = PyDict_GetItemString(dict, "__slots__");        // 获取属性字典中是否有__slots__值存在,该值是限制类中有什么属性nslots = 0;add_dict = 0;add_weak = 0;may_add_dict = base->tp_dictoffset == 0;               // __dict__相对于类的偏移may_add_weak = base->tp_weaklistoffset == 0 && base->tp_itemsize == 0;  // 弱引用列表if (slots == NULL) {                                   // 如果为空则全部置1if (may_add_dict) {add_dict++;}if (may_add_weak) {add_weak++;}}else {/* Have slots *//* Make it into a tuple */if (PyString_Check(slots))                        // 如果是字符串,则转换成包含一个元素的元组slots = PyTuple_Pack(1, slots);elseslots = PySequence_Tuple(slots);              // 如果是列表,则转换成元组if (slots == NULL) {Py_DECREF(bases);return NULL;}assert(PyTuple_Check(slots));                    // 检查是否转换成元组/* Are slots allowed? */nslots = PyTuple_GET_SIZE(slots);                // 获取元组的大小if (nslots > 0 && base->tp_itemsize != 0) {PyErr_Format(PyExc_TypeError,"nonempty __slots__ ""not supported for subtype of '%s'",base->tp_name);bad_slots:Py_DECREF(bases);Py_DECREF(slots);return NULL;}#ifdef Py_USING_UNICODEtmp = _unicode_to_string(slots, nslots);       // 进行字符集转换if (tmp != slots) {Py_DECREF(slots);slots = tmp;}if (!tmp)return NULL;
#endif/* Check for valid slot names and two special cases */for (i = 0; i < nslots; i++) {PyObject *tmp = PyTuple_GET_ITEM(slots, i);   // 依次获取对应的字符串char *s;if (!valid_identifier(tmp))                   // 检查字符串是否合法goto bad_slots;assert(PyString_Check(tmp));s = PyString_AS_STRING(tmp);                  // 获取值if (strcmp(s, "__dict__") == 0) {             // 如果是__dict__,则add_dict加1if (!may_add_dict || add_dict) {PyErr_SetString(PyExc_TypeError,"__dict__ slot disallowed: ""we already got one");goto bad_slots;}add_dict++;}if (strcmp(s, "__weakref__") == 0) {          // 如果是__weakref__,则add_weak加1if (!may_add_weak || add_weak) {PyErr_SetString(PyExc_TypeError,"__weakref__ slot disallowed: ""either we already got one, ""or __itemsize__ != 0");goto bad_slots;}add_weak++;}}/* Copy slots into yet another tuple, demangling names */newslots = PyTuple_New(nslots - add_dict - add_weak);    // 因为不能创建__dict__和__weakref__,所有要减去这个字符串的数量,新建新的元组if (newslots == NULL)goto bad_slots;for (i = j = 0; i < nslots; i++) {char *s;tmp = PyTuple_GET_ITEM(slots, i);s = PyString_AS_STRING(tmp);if ((add_dict && strcmp(s, "__dict__") == 0) ||(add_weak && strcmp(s, "__weakref__") == 0))continue;tmp =_Py_Mangle(name, tmp);if (!tmp)goto bad_slots;PyTuple_SET_ITEM(newslots, j, tmp);             // 如果slots中的字符串不是__dict__和__weakref__,则设置到新的元组中j++;}assert(j == nslots - add_dict - add_weak);nslots = j;Py_DECREF(slots);slots = newslots;                                   // 将slots重置/* Secondary bases may provide weakrefs or dict */if (nbases > 1 &&((may_add_dict && !add_dict) ||(may_add_weak && !add_weak))) {for (i = 0; i < nbases; i++) {tmp = PyTuple_GET_ITEM(bases, i);if (tmp == (PyObject *)base)continue; /* Skip primary base */if (PyClass_Check(tmp)) {/* Classic base class provides both */if (may_add_dict && !add_dict)add_dict++;if (may_add_weak && !add_weak)add_weak++;break;}assert(PyType_Check(tmp));tmptype = (PyTypeObject *)tmp;if (may_add_dict && !add_dict &&tmptype->tp_dictoffset != 0)add_dict++;if (may_add_weak && !add_weak &&tmptype->tp_weaklistoffset != 0)add_weak++;if (may_add_dict && !add_dict)continue;if (may_add_weak && !add_weak)continue;/* Nothing more to check */break;}}}/* XXX From here until type is safely allocated,"return NULL" may leak slots! *//* Allocate the type object */type = (PyTypeObject *)metatype->tp_alloc(metatype, nslots);       // 调用比较后的元类进行,申请内存大小,如果传入的是Object,则是调用了type中的tp_allocif (type == NULL) {                                                // type中的tp_alloc是获取tp_basicsize+tp_itemsize,此时type中的大小sizeof(PyHeapTypeObject)+sizeof(PyMemberDef)Py_XDECREF(slots);Py_DECREF(bases);return NULL;}/* Keep name and slots alive in the extended type object */et = (PyHeapTypeObject *)type;                                    Py_INCREF(name);et->ht_name = name;                                             // 设置类名et->ht_slots = slots;                                           // 设置设置属性的列表数量/* Initialize tp_flags */type->tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HEAPTYPE |Py_TPFLAGS_BASETYPE;if (base->tp_flags & Py_TPFLAGS_HAVE_GC)type->tp_flags |= Py_TPFLAGS_HAVE_GC;/* It's a new-style number unless it specifically inherits anyold-style numeric behavior */if ((base->tp_flags & Py_TPFLAGS_CHECKTYPES) ||(base->tp_as_number == NULL))type->tp_flags |= Py_TPFLAGS_CHECKTYPES;/* Initialize essential fields */type->tp_as_number = &et->as_number;                        // 设置PyTypeObject中各个域的值type->tp_as_sequence = &et->as_sequence;type->tp_as_mapping = &et->as_mapping;type->tp_as_buffer = &et->as_buffer;type->tp_name = PyString_AS_STRING(name);/* Set tp_base and tp_bases */type->tp_bases = bases;                                     // 设置PyTypeObject的基类列表Py_INCREF(base);type->tp_base = base;                                       // 设置父类/* Initialize tp_dict from passed-in dict */type->tp_dict = dict = PyDict_Copy(dict);                   // 设置PyTypeObject的tp_dict属性,将传入的属性拷贝到tp_dict中if (dict == NULL) {Py_DECREF(type);return NULL;}/* Set __module__ in the dict */if (PyDict_GetItemString(dict, "__module__") == NULL) {              // 在属性列表中设置__module__属性值tmp = PyEval_GetGlobals();if (tmp != NULL) {tmp = PyDict_GetItemString(tmp, "__name__");if (tmp != NULL) {if (PyDict_SetItemString(dict, "__module__",tmp) < 0)return NULL;}}}/* Set tp_doc to a copy of dict['__doc__'], if the latter is thereand is a string.  The __doc__ accessor will first look for tp_doc;if that fails, it will still look into __dict__.*/{PyObject *doc = PyDict_GetItemString(dict, "__doc__");          // 设置属性列表中__doc__的值if (doc != NULL && PyString_Check(doc)) {const size_t n = (size_t)PyString_GET_SIZE(doc);char *tp_doc = (char *)PyObject_MALLOC(n+1);if (tp_doc == NULL) {Py_DECREF(type);return NULL;}memcpy(tp_doc, PyString_AS_STRING(doc), n+1);type->tp_doc = tp_doc;}}/* Special-case __new__: if it's a plain function,make it a static function */tmp = PyDict_GetItemString(dict, "__new__");                       // 如果属性列表中重写了__new__则将该方法设置成静态方法if (tmp != NULL && PyFunction_Check(tmp)) {tmp = PyStaticMethod_New(tmp);if (tmp == NULL) {Py_DECREF(type);return NULL;}PyDict_SetItemString(dict, "__new__", tmp);                   // 景生成的静态方法覆盖属性列表中的值Py_DECREF(tmp);}/* Add descriptors for custom slots from __slots__, or for __dict__ */mp = PyHeapType_GET_MEMBERS(et);                                  // 获取机制的偏移量slotoffset = base->tp_basicsize;                                  // 获取机制偏移量if (slots != NULL) {                                              // 如果slots不为空for (i = 0; i < nslots; i++, mp++) {                          // 如果有值,则添加mp->name = PyString_AS_STRING(PyTuple_GET_ITEM(slots, i));mp->type = T_OBJECT_EX;mp->offset = slotoffset;if (base->tp_weaklistoffset == 0 &&strcmp(mp->name, "__weakref__") == 0) {add_weak++;mp->type = T_OBJECT;mp->flags = READONLY;type->tp_weaklistoffset = slotoffset;}slotoffset += sizeof(PyObject *);}}if (add_dict) {                                                 // 如果add_dict有值if (base->tp_itemsize)type->tp_dictoffset = -(long)sizeof(PyObject *);elsetype->tp_dictoffset = slotoffset;slotoffset += sizeof(PyObject *);                           // 添加一个字节偏移量}if (add_weak) {assert(!base->tp_itemsize);type->tp_weaklistoffset = slotoffset;slotoffset += sizeof(PyObject *);                           // 添加一个字节偏移量}type->tp_basicsize = slotoffset;                                // 偏移的总大小type->tp_itemsize = base->tp_itemsize; type->tp_members = PyHeapType_GET_MEMBERS(et);// 设置相关域属性if (type->tp_weaklistoffset && type->tp_dictoffset)  type->tp_getset = subtype_getsets_full;else if (type->tp_weaklistoffset && !type->tp_dictoffset)type->tp_getset = subtype_getsets_weakref_only;else if (!type->tp_weaklistoffset && type->tp_dictoffset)type->tp_getset = subtype_getsets_dict_only;elsetype->tp_getset = NULL;/* Special case some slots */if (type->tp_dictoffset != 0 || nslots > 0) {if (base->tp_getattr == NULL && base->tp_getattro == NULL)type->tp_getattro = PyObject_GenericGetAttr;if (base->tp_setattr == NULL && base->tp_setattro == NULL)type->tp_setattro = PyObject_GenericSetAttr;}type->tp_dealloc = subtype_dealloc;/* Enable GC unless there are really no instance variables possible */if (!(type->tp_basicsize == sizeof(PyObject) &&type->tp_itemsize == 0))type->tp_flags |= Py_TPFLAGS_HAVE_GC;/* Always override allocation strategy to use regular heap */type->tp_alloc = PyType_GenericAlloc;if (type->tp_flags & Py_TPFLAGS_HAVE_GC) {type->tp_free = PyObject_GC_Del;type->tp_traverse = subtype_traverse;type->tp_clear = subtype_clear;}elsetype->tp_free = PyObject_Del;/* Initialize the rest */if (PyType_Ready(type) < 0) {            // 初始化该type的类型Py_DECREF(type);return NULL;}/* Put the proper slots in place */fixup_slot_dispatchers(type);           // 更新相应的描述符return (PyObject *)type;                // 返回生成的对象
}

type_new的工作相对复杂,主要工作是确定元类,确定最佳基类,然后申请内存,然后设置相关域的值,设置完成后初始化该类,然后更新相应的描述符,然后返回初始化后的type。在本例中,由于元类是type,最佳父类是Object所以对于的申请内存,调用初始化都是通过调用相应方法来实现的,此时自定义类初始化完成。
此时我们继续查看脚本文件的字节码的执行流程,此时执行到了;

 15          22 LOAD_NAME                1 (A)25 CALL_FUNCTION            028 STORE_NAME               2 (a)

此时初始化了类A到一个实例a,先获取刚刚创建的类A,然后调用CALL_FUNCTION在该函数执行流程,由于传入的A不是函数对象而是类,所有会执行do_call;

static PyObject *
call_function(PyObject ***pp_stack, int oparg
#ifdef WITH_TSC, uint64* pintr0, uint64* pintr1
#endif)
{...if (PyFunction_Check(func))                                     // 检查是否是函数类型x = fast_function(func, pp_stack, n, na, nk);               // 处理快速方法elsex = do_call(func, pp_stack, na, nk);...
}

我们查看do_call方法,执行了哪些操作;

static PyObject *
do_call(PyObject *func, PyObject ***pp_stack, int na, int nk)
{...   // 参数,传入函数的检查,将参数进行转换result = PyObject_Call(func, callargs, kwdict);call_fail:Py_XDECREF(callargs);Py_XDECREF(kwdict);return result;
}

此时调用了PyObject_Call,调用了跟在创建类时使用的方法。此时将会调用class A的type的tp_new函数,而此时class A的type任然是PyType_Type,所以还是调用该类型的tp_new,但是在初始化class A时会让A继承基类的操作,当时A初始化时没有定义tp_new操作,所以会继承object的tp_new,此时,在调用class A的tp_new时,将会调用object的tp_new指向的方法-object_new;

static PyObject *
object_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
{if (type->tp_init == object_init && (PyTuple_GET_SIZE(args) ||(kwds && PyDict_Check(kwds) && PyDict_Size(kwds)))) {PyErr_SetString(PyExc_TypeError,"default __new__ takes no parameters");return NULL;}return type->tp_alloc(type, 0);    // 调用PyBaseObject_Type的PyType_GenericAlloc
}

其中调用PyType_GenericAlloc,会将生成实例的ob-type改为该类

PyObject *
PyType_GenericAlloc(PyTypeObject *type, Py_ssize_t nitems)
{PyObject *obj;const size_t size = _PyObject_VAR_SIZE(type, nitems+1);/* note that we need to add one, for the sentinel */if (PyType_IS_GC(type))obj = _PyObject_GC_Malloc(size);elseobj = (PyObject *)PyObject_MALLOC(size);if (obj == NULL)return PyErr_NoMemory();memset(obj, '\0', size);if (type->tp_flags & Py_TPFLAGS_HEAPTYPE)Py_INCREF(type);if (type->tp_itemsize == 0)PyObject_INIT(obj, type);else(void) PyObject_INIT_VAR((PyVarObject *)obj, type, nitems);if (PyType_IS_GC(type))_PyObject_GC_TRACK(obj);return obj;
}// 将生成的类型的ob_type更改为生成的类
#define PyObject_INIT(op, typeobj) \( (op)->ob_type = (typeobj), _Py_NewReference((PyObject *)(op)), (op) )
#define PyObject_INIT_VAR(op, typeobj, size) \( (op)->ob_size = (size), PyObject_INIT((op), (typeobj)) 

调用了PyType_GenericAlloc申请内存,然后就完成了类的初始化完成。在申请内存完成后,返回type_new后将判断执行的是否是生成实例,如果是实例并且有init方法,则调用init方法进行初始化工作。但是在本例中,因为自定义了init方法,所有会在fixup_slot_dispatchers来更新用户自定义的方法,此时的init方法就会被替换成slot_tp_init,该方法就是调用自身重写的init方法来进行初始化。初始化完成后就生成了a的实例。

继续查看脚本文件中的字节码;

 16          31 LOAD_NAME                2 (a)34 LOAD_ATTR                3 (f)37 CALL_FUNCTION            040 POP_TOP  

此时就是调用a.f()来访问实例上的属性,根据执行的字节码可知,先加载实例a,然后调用LOAD_ATTR,此时我们查看该字节码执行的流程;

        case LOAD_ATTR:w = GETITEM(names, oparg);v = TOP();x = PyObject_GetAttr(v, w);Py_DECREF(v);SET_TOP(x);if (x != NULL) continue;

先获取names中的f,然后在将实例a和f作为参数,传入PyObject_GetAttr中去获取属性;

PyObject *
PyObject_GetAttr(PyObject *v, PyObject *name)
{PyTypeObject *tp = v->ob_type;                       // 传入实例的类型,此时就是生成类A的类型if (!PyString_Check(name)) {                         // 检查传入的属性是否是字符串
#ifdef Py_USING_UNICODE/* The Unicode to string conversion is done here because theexisting tp_getattro slots expect a string object as nameand we wouldn't want to break those. */if (PyUnicode_Check(name)) {name = _PyUnicode_AsDefaultEncodedString(name, NULL);if (name == NULL)return NULL;}else
#endif{PyErr_Format(PyExc_TypeError,"attribute name must be string, not '%.200s'",name->ob_type->tp_name);return NULL;}}if (tp->tp_getattro != NULL)                        // 如果tp_getattro不为空,先调用该方法查找return (*tp->tp_getattro)(v, name);if (tp->tp_getattr != NULL)                         // 如果tp_getattro为空,则调用tp_getattr查找return (*tp->tp_getattr)(v, PyString_AS_STRING(name));PyErr_Format(PyExc_AttributeError,"'%.50s' object has no attribute '%.400s'",tp->tp_name, PyString_AS_STRING(name));return NULL;
}

由于类A继承了object所以tp->tp_getattro就是调用了object中的PyObject_GenericGetAttr,对应代码如下;

PyObject *
PyObject_GenericGetAttr(PyObject *obj, PyObject *name)
{PyTypeObject *tp = obj->ob_type;                // 获取类的类型,此时传入为APyObject *descr = NULL;PyObject *res = NULL;descrgetfunc f;Py_ssize_t dictoffset;                          // 相对于tp的__dict__的偏移量PyObject **dictptr;if (!PyString_Check(name)){                     // 检查传入的名称是否为字符串
#ifdef Py_USING_UNICODE/* The Unicode to string conversion is done here because theexisting tp_setattro slots expect a string object as nameand we wouldn't want to break those. */if (PyUnicode_Check(name)) {name = PyUnicode_AsEncodedString(name, NULL, NULL);if (name == NULL)return NULL;}else
#endif{PyErr_Format(PyExc_TypeError,"attribute name must be string, not '%.200s'",name->ob_type->tp_name);return NULL;}}elsePy_INCREF(name);if (tp->tp_dict == NULL) {                      // 如果类型为空,则先初始化该类型if (PyType_Ready(tp) < 0)goto done;}/* Inline _PyType_Lookup */                     // 先从父类的tp_dict中查找f对应的descr{Py_ssize_t i, n;PyObject *mro, *base, *dict;/* Look in tp_dict of types in MRO */mro = tp->tp_mro;assert(mro != NULL);assert(PyTuple_Check(mro));n = PyTuple_GET_SIZE(mro);                          // 获取mro的大小for (i = 0; i < n; i++) {                           // 依次偏移mro列表base = PyTuple_GET_ITEM(mro, i);                // 获取对应的基类if (PyClass_Check(base))dict = ((PyClassObject *)base)->cl_dict;else {assert(PyType_Check(base));dict = ((PyTypeObject *)base)->tp_dict;}assert(dict && PyDict_Check(dict));             // 从mro类的中的dict中查找是否有name 存在descr = PyDict_GetItem(dict, name);if (descr != NULL)break;}}Py_XINCREF(descr);f = NULL;if (descr != NULL &&PyType_HasFeature(descr->ob_type, Py_TPFLAGS_HAVE_CLASS)) {f = descr->ob_type->tp_descr_get;                   // 如果在mro中查找到了,则调用对应类型的__get__方法if (f != NULL && PyDescr_IsData(descr)) {           // 如果descr中,对应的__set__方法不为空res = f(descr, obj, (PyObject *)obj->ob_type);  // 执行该方法并返回结果值Py_DECREF(descr);goto done;}}/* Inline _PyObject_GetDictPtr */dictoffset = tp->tp_dictoffset;                         // 获取该类的__dict__的偏移量if (dictoffset != 0) {PyObject *dict;if (dictoffset < 0) {Py_ssize_t tsize;size_t size;tsize = ((PyVarObject *)obj)->ob_size;if (tsize < 0)tsize = -tsize;size = _PyObject_VAR_SIZE(tp, tsize);dictoffset += (long)size;assert(dictoffset > 0);assert(dictoffset % SIZEOF_VOID_P == 0);}dictptr = (PyObject **) ((char *)obj + dictoffset);   // 获取对应的__dict__对应的地址dict = *dictptr;                                      // 获取实例的字典if (dict != NULL) {res = PyDict_GetItem(dict, name);                 // 在实例字典中,查找name,获取对应的方法if (res != NULL) {Py_INCREF(res);Py_XDECREF(descr);goto done;}}}if (f != NULL) {                                          // 如果有__get__ ,在实例中的__dict__中,没有找到,并且__set__为空res = f(descr, obj, (PyObject *)obj->ob_type);     Py_DECREF(descr);                                     // 则调用对应的__get__goto done;}if (descr != NULL) {                                      // 如果以上都没有,但是在基类中找到了对应的descr,则直接调用该函数res = descr;                                        /* descr was already increfed above */goto done;}PyErr_Format(PyExc_AttributeError,"'%.50s' object has no attribute '%.400s'",tp->tp_name, PyString_AS_STRING(name));done:Py_DECREF(name);return res;
}

这个属性的查找机制,相对有点复杂,首先;
查找的descr大致分为两种,
1,data descriptor,定义了getset的两种方法;
2,not data descriptor,定义了get的方法
选择流程如下;
1,如果在父类中查找到了data descriptor对应的属性,则直接调用该属性;
2,如果第一步没找到,则再查找实例属性对应的属性,如果没找到则查找类对应的属性。
此时在本例中,A.mro为(A,object),此时再mro的查找中会找到对应的函数f,然后会获取对应的descr,此时会在f = descr->ob_type->tp_descr_get;将PyFunctionObject的ob_type的tp_descr_get方法赋值给f;当继续执行时,由于不符合data descriptor所以会执行到;

    if (f != NULL) {                                          // 如果有__get__ ,在实例中的__dict__中,没有找到,并且__set__为空res = f(descr, obj, (PyObject *)obj->ob_type);     Py_DECREF(descr);                                     // 则调用对应的__get__goto done;}   if (f != NULL) {                                          // 如果有__get__ ,在实例中的__dict__中,没有找到,并且__set__为空res = f(descr, obj, (PyObject *)obj->ob_type);     Py_DECREF(descr);                                     // 则调用对应的__get__goto done;}

所以g中的self操作,都隐藏在PyFunctionType的ob_type的tp_descr_get中,此时我们查看;

static PyObject *
func_descr_get(PyObject *func, PyObject *obj, PyObject *type)
{if (obj == Py_None)obj = NULL;return PyMethod_New(func, obj, type);
}

调用了PyMethod_New方法;

PyObject *
PyMethod_New(PyObject *func, PyObject *self, PyObject *klass)
{register PyMethodObject *im;if (!PyCallable_Check(func)) {PyErr_BadInternalCall();return NULL;}im = free_list;                                         // 缓存池概念if (im != NULL) {free_list = (PyMethodObject *)(im->im_self);PyObject_INIT(im, &PyMethod_Type);}else {im = PyObject_GC_New(PyMethodObject, &PyMethod_Type);if (im == NULL)return NULL;}im->im_weakreflist = NULL;Py_INCREF(func);im->im_func = func;                         // 设置要执行的函数Py_XINCREF(self);im->im_self = self;                         // 设置类的selfPy_XINCREF(klass); im->im_class = klass;                       // 设置类的值_PyObject_GC_TRACK(im);return (PyObject *)im;
}

此时可以看见传入的实例obj,作为了self,存入了im-im_self中,然后将PyMethodObject作为参数返回。此时,再调用CALL_FUNCTION时,

static PyObject *
call_function(PyObject ***pp_stack, int oparg
#ifdef WITH_TSC, uint64* pintr0, uint64* pintr1
#endif)
{int na = oparg & 0xff;         // 获取输入参数的个数int nk = (oparg>>8) & 0xff;    // 获取位置参数的个数int n = na + 2 * nk;           //  总大小,由于一个位置参数由key和value组成,所有乘2PyObject **pfunc = (*pp_stack) - n - 1;      // 获取函数对象PyObject *func = *pfunc;PyObject *x, *w;/* Always dispatch PyCFunction first, because these arepresumed to be the most frequent callable object.*/if (PyCFunction_Check(func) && nk == 0) {           // 检查func的类型,是否为cfunc...} else {if (PyMethod_Check(func) && PyMethod_GET_SELF(func) != NULL) {  // 检查func是否是类访问的方法/* optimize access to bound methods */PyObject *self = PyMethod_GET_SELF(func);                   // 获取对应的实例PCALL(PCALL_METHOD);PCALL(PCALL_BOUND_METHOD);Py_INCREF(self);func = PyMethod_GET_FUNCTION(func);                         // 获取对应的funcPy_INCREF(func);Py_DECREF(*pfunc);*pfunc = self;                                              // 将pfunc设置成实例自己,相当于将实例压栈na++;                                                       // 位置参数加1n++;                                                        // 参数总数加1} elsePy_INCREF(func);READ_TIMESTAMP(*pintr0);if (PyFunction_Check(func))                                     // 检查是否是函数类型x = fast_function(func, pp_stack, n, na, nk);               // 处理快速方法elsex = do_call(func, pp_stack, na, nk);READ_TIMESTAMP(*pintr1);Py_DECREF(func);}...
}

此时,相当于默认添加了一个默认的位置参数,然后快速调用fast_function此时的执行流程与分析过的函数调用时一样的了,至此无参函数的调用就分析完成。解析来分析有参函数的执行流程。

继续执教脚本字节码;

 17          41 LOAD_NAME                2 (a)44 LOAD_ATTR                4 (g)47 LOAD_CONST               2 (5)50 CALL_FUNCTION            153 POP_TOP             54 LOAD_CONST               3 (None)57 RETURN_VALUE 

此时就是调用了a.g(5),此时先加载了实例,然后调用LOAD_ATTR g,接着LOAD_CONST 函数参数5,此时调用了CALL_FUNCTION,此时根据无参函数的分析可知,此时就压栈了一个常量5作为函数的位置参数,接下来的流程就与有参函数的调用流程是一样的,主要分析一下对应的g的字节码;

  8           0 LOAD_FAST                1 (aValue)3 LOAD_FAST                0 (self)6 STORE_ATTR               0 (aValue)9           9 LOAD_FAST                0 (self)12 LOAD_ATTR                0 (aValue)15 PRINT_ITEM          16 PRINT_NEWLINE       10          17 LOAD_CONST               1 ('A.g')20 PRINT_ITEM          21 PRINT_NEWLINE       22 LOAD_CONST               0 (None)25 RETURN_VALUE 

主要在于先LOAD_FAST,获取了输入的参数值,然后加载了self,然后调用了STORE_ATTR;

        case STORE_ATTR:w = GETITEM(names, oparg);    // 获取对应names中的值v = TOP();                   // 对应压入的selfu = SECOND();                // 对应压入的aValueSTACKADJ(-2);err = PyObject_SetAttr(v, w, u); /* v.w = u */Py_DECREF(v);Py_DECREF(u);if (err == 0) continue;break;

此时,查看PyObject_SetAttr;

int
PyObject_SetAttr(PyObject *v, PyObject *name, PyObject *value)
{PyTypeObject *tp = v->ob_type;                                      // 获取类Aint err;if (!PyString_Check(name)){                                         // 检查传入名称是否是字符串
#ifdef Py_USING_UNICODE/* The Unicode to string conversion is done here because theexisting tp_setattro slots expect a string object as nameand we wouldn't want to break those. */if (PyUnicode_Check(name)) {name = PyUnicode_AsEncodedString(name, NULL, NULL);if (name == NULL)return -1;}else
#endif{PyErr_Format(PyExc_TypeError,"attribute name must be string, not '%.200s'",name->ob_type->tp_name);return -1;}}elsePy_INCREF(name);PyString_InternInPlace(&name);if (tp->tp_setattro != NULL) {err = (*tp->tp_setattro)(v, name, value);                       // 调用类A的tp_setattro方法,由于此时对应于object中的tp_setattroPy_DECREF(name);return err;}if (tp->tp_setattr != NULL) {                                       // 如果tp_setattro为空,则调用tp_setattr设置属性err = (*tp->tp_setattr)(v, PyString_AS_STRING(name), value);Py_DECREF(name);return err;}Py_DECREF(name);if (tp->tp_getattr == NULL && tp->tp_getattro == NULL)              // 如果类型两个方法都没有则报错PyErr_Format(PyExc_TypeError,"'%.100s' object has no attributes ""(%s .%.100s)",tp->tp_name,value==NULL ? "del" : "assign to",PyString_AS_STRING(name));elsePyErr_Format(PyExc_TypeError,"'%.100s' object has only read-only attributes ""(%s .%.100s)",tp->tp_name,value==NULL ? "del" : "assign to",PyString_AS_STRING(name));return -1;
}

此时,由于类A继承了object的tp_setattro,所以调用了PyObject_GenericSetAttr;

int
PyObject_GenericSetAttr(PyObject *obj, PyObject *name, PyObject *value)
{PyTypeObject *tp = obj->ob_type;PyObject *descr;descrsetfunc f;PyObject **dictptr;int res = -1;if (!PyString_Check(name)){
#ifdef Py_USING_UNICODE/* The Unicode to string conversion is done here because theexisting tp_setattro slots expect a string object as nameand we wouldn't want to break those. */if (PyUnicode_Check(name)) {name = PyUnicode_AsEncodedString(name, NULL, NULL);if (name == NULL)return -1;}else
#endif{PyErr_Format(PyExc_TypeError,"attribute name must be string, not '%.200s'",name->ob_type->tp_name);return -1;}}elsePy_INCREF(name);if (tp->tp_dict == NULL) {                          // 如果属性为空则先初始化if (PyType_Ready(tp) < 0)goto done;}descr = _PyType_Lookup(tp, name);                   // 查找mro中是否有该属性f = NULL;if (descr != NULL &&PyType_HasFeature(descr->ob_type, Py_TPFLAGS_HAVE_CLASS)) {f = descr->ob_type->tp_descr_set;if (f != NULL && PyDescr_IsData(descr)) {res = f(descr, obj, value);                 // 如果有则重置该属性goto done;}}dictptr = _PyObject_GetDictPtr(obj);                // 获取__dict__相对于实例的位移if (dictptr != NULL) {PyObject *dict = *dictptr;if (dict == NULL && value != NULL) {            // 如果没有则生成一个属性dictdict = PyDict_New();if (dict == NULL)goto done;*dictptr = dict;                            // 将该字典的位置设置到实例对应的__dict__z中        } if (dict != NULL) {if (value == NULL)res = PyDict_DelItem(dict, name);       // 如果传入的值为空,则删除该属性elseres = PyDict_SetItem(dict, name, value);            // 如果传入的有值,则设置该属性到__dict__中if (res < 0 && PyErr_ExceptionMatches(PyExc_KeyError))PyErr_SetObject(PyExc_AttributeError, name);goto done;}}if (f != NULL) {res = f(descr, obj, value); goto done;}if (descr == NULL) {PyErr_Format(PyExc_AttributeError,"'%.100s' object has no attribute '%.200s'",tp->tp_name, PyString_AS_STRING(name));goto done;}PyErr_Format(PyExc_AttributeError,"'%.50s' object attribute '%.400s' is read-only",tp->tp_name, PyString_AS_STRING(name));done:Py_DECREF(name);return res;
}

至此,属性的设置流程执行完成。
以上就是有关Python中类的基本操作,如有兴趣可自行查看。

Python源码学习:Python类机制分析-用户自定义类相关推荐

  1. Python源码学习:Python函数浅析-无参函数

    Python源码分析 本文环境python2.5系列 参考书籍<<Python源码剖析>> 本文会大致分析一下Python中的函数机制.在Python中,函数是一个比较重要的类 ...

  2. Python源码学习:Python函数浅析-函数闭包

    Python源码分析 本文环境python2.5系列 参考书籍<<Python源码剖析>> 上一篇分析了函数参数的分析后,本文分析函数闭包的实现.函数闭包即函数定义和函数表达式 ...

  3. Python源码学习:Python类机制分析

    Python源码分析 本文环境python2.5系列 参考书籍<<Python源码剖析>> 本文主要分析Python中类时如何实现的,在Python中,一切都是对象:任何对象都 ...

  4. Python源码学习:多线程实现机制

    Python源码分析 本文环境python2.5系列 参考书籍<<Python源码剖析>> 本文分析Python中的多线程机制,主要通过一个多线程的脚本来分析多线程的基本操作与 ...

  5. Python源码学习:Python函数浅析-有参函数

    Python源码分析 本文环境python2.5系列 参考书籍<<Python源码剖析>> 继续上一篇无参函数的调用后,本文将分析Python中的有参函数的大致流程,在Pyth ...

  6. Python源码学习:启动流程简析

    Python源码分析 本文环境python2.5系列 参考书籍<<Python源码剖析>> Python简介: python主要是动态语言,虽然Python语言也有编译,生成中 ...

  7. 2021-03-19Tomcat源码学习--WebAppClassLoader类加载机制

    Tomcat源码学习--WebAppClassLoader类加载机制 在WebappClassLoaderBase中重写了ClassLoader的loadClass方法,在这个实现方法中我们可以一窥t ...

  8. python源码学习_【Python学习】Python源码阅读(一)

    最近想读读Python源码,任何东西学习方法基本都是一样的,先从总体框架进行了解,再从自己侧重的方面逐步深入. 1. Python总体架构 左边是Python提供的大量的模块.库以及用户自定义的模块. ...

  9. Python 源码学习:类型和对象

    Python 是一门解释型,动态类型,多范式的编程语言,当我们从 python.org 下载并安装运行 Python 的某个分发版本时,我们实际上是在运行由 C 语言编写的 CPython ,除此之外 ...

最新文章

  1. 详细讲解np.cumsum()
  2. 大厂面试录取通过率不到3%,我真是太太太难了......
  3. 并发编程之多进程进程进程
  4. 2020年日历电子版(打印版)_2020年第11期印花世界电子版/手机版,欢迎在线免费阅读!...
  5. spring-boot-devtools
  6. Swift入门 新浪微博
  7. Python精选库大全,建议收藏留用!
  8. POJ 2243:Knight Moves(双向BFS)
  9. 物联网的应用涉及生活的方方面面,在这里介绍一下物联网的多种应用场景
  10. 亲测有效 破解 锐捷限制校园网多网卡 win10用校内网在虚拟机里开win7
  11. X度网盘大文件使用浏览器或迅雷下载的方法之一
  12. Linux audit详解
  13. 自然语言处理基于java实现(1) 之 中文分词
  14. C语言结构体struck所占用的字节数如何计算
  15. red hat linux 9.0下载地址集合,Red Hat Linux 9.0 iso最新下载地址
  16. idea启动过多导致C盘空间不足
  17. 编程时拼音输入法的设置
  18. [RoCE]RDMA over Converged Ethernet模式以及配置
  19. 知乎:人工智能最终会代替数学家或理论物理学家吗?
  20. 九度oj 第1题 二维数组中的查找 何海涛:《剑指Offer:名企面试官精讲典型编程题》

热门文章

  1. 人工干预如何提高模型性能?看这文就够了!
  2. 自研芯片架构 ,这家中国公司发布DPU芯片计划
  3. 高文、张钹、杨强隔空论道:AI精度与隐私的博弈
  4. 把自己朝九晚五的工作自动化了,有错吗?
  5. 通俗易懂:图卷积神经网络入门详解
  6. 新一届最强预训练模型上榜,出于BERT而胜于BERT
  7. 中科院、百度研究院等联合提出UGAN,生成图片难以溯源
  8. 如何使用 Spring 实现策略模式+工厂模式
  9. 万万没想到! logger.info() 还能导致线上故障?
  10. 面进了心心念念的国企!以为TM上岸了!干了1年!我却再次选择回到互联网大厂内卷!