普通属性和魔术属性
普通属性都存储在__dict__
属性中,
而魔术属性由Python决定存储位置,有的魔术属性有特殊的存储方式,也可以跟普通属性一样存储在__dict__
中。
重新赋值的魔术方法都存储在__dict__
中。
魔术属性和普通属性的区别是魔术属性在解释器中有专门的处理函数,
比如__dict__
的_PyObject_GetDictPtr
等,__doc__
的type_set_doc
等为这些魔术属性进行特别的处理。
而普通属性是使用通用的函数处理。
我们可以用Python的源码中查找它们的定义和使用流程。
object和type源码
先看看object
和type
这两个最重要的基类的源码。
object
类型
typedef struct _object {
_PyObject_HEAD_EXTRA //宏
Py_ssize_t ob_refcnt; //typedef _W64 int Py_ssize_t;
PyTypeObject *ob_type;
} PyObject;
可见PyObject只有一个PyTypeObject
变量,这个变量其实就是__class__
type
类型
typedef struct _typeobject {
PyObject_VAR_HEAD
const char *tp_name; /* For printing, in format "<module>.<name>" */
Py_ssize_t tp_basicsize, tp_itemsize; /* For allocation */
/* Methods to implement standard operations */
destructor tp_dealloc;
Py_ssize_t tp_vectorcall_offset;
getattrfunc tp_getattr;
setattrfunc tp_setattr;
PyAsyncMethods *tp_as_async; /* formerly known as tp_compare (Python 2)
or tp_reserved (Python 3) */
reprfunc tp_repr;
/* Method suites for standard classes */
PyNumberMethods *tp_as_number;
PySequenceMethods *tp_as_sequence;
PyMappingMethods *tp_as_mapping;
/* More standard operations (here for binary compatibility) */
hashfunc tp_hash;
ternaryfunc tp_call;
reprfunc tp_str;
getattrofunc tp_getattro;
setattrofunc tp_setattro;
/* Functions to access object as input/output buffer */
PyBufferProcs *tp_as_buffer;
/* Flags to define presence of optional/expanded features */
unsigned long tp_flags;
const char *tp_doc; /* Documentation string */
/* Assigned meaning in release 2.0 */
/* call function for all accessible objects */
traverseproc tp_traverse;
/* delete references to contained objects */
inquiry tp_clear;
/* Assigned meaning in release 2.1 */
/* rich comparisons */
richcmpfunc tp_richcompare;
/* weak reference enabler */
Py_ssize_t tp_weaklistoffset;
/* Iterators */
getiterfunc tp_iter;
iternextfunc tp_iternext;
/* Attribute descriptor and subclassing stuff */
struct PyMethodDef *tp_methods;
struct PyMemberDef *tp_members;
struct PyGetSetDef *tp_getset;
// Strong reference on a heap type, borrowed reference on a static type
struct _typeobject *tp_base;
PyObject *tp_dict;
descrgetfunc tp_descr_get;
descrsetfunc tp_descr_set;
Py_ssize_t tp_dictoffset;
initproc tp_init;
allocfunc tp_alloc;
newfunc tp_new;
freefunc tp_free; /* Low-level free-memory routine */
inquiry tp_is_gc; /* For PyObject_IS_GC */
PyObject *tp_bases;
PyObject *tp_mro; /* method resolution order */
PyObject *tp_cache;
PyObject *tp_subclasses;
PyObject *tp_weaklist;
destructor tp_del;
/* Type attribute cache version tag. Added in version 2.6 */
unsigned int tp_version_tag;
destructor tp_finalize;
vectorcallfunc tp_vectorcall;
} PyTypeObject;
__dict__
__dict__
是魔术属性
一个对象有没有__dict__
属性决定了它能不能定义自己的属性,即能否setattr(obj, name, value)
。
明明PyObject
中只有一个__class__
的属性,那我们在Python中使用的__dict__
属性是从何而来?
在源码中找到_PyObject_GetDictPtr
PyObject **
_PyObject_GetDictPtr(PyObject *obj)
{
Py_ssize_t dictoffset;
PyTypeObject *tp = Py_TYPE(obj);
dictoffset = tp->tp_dictoffset;
if (dictoffset == 0)
return NULL;
if (dictoffset < 0) {
Py_ssize_t tsize = Py_SIZE(obj);
if (tsize < 0) {
tsize = -tsize;
}
size_t size = _PyObject_VAR_SIZE(tp, tsize);
dictoffset += (long)size;
_PyObject_ASSERT(obj, dictoffset > 0);
_PyObject_ASSERT(obj, dictoffset % SIZEOF_VOID_P == 0);
}
return (PyObject **) ((char *)obj + dictoffset);
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RgTdJRO4-1625901399332)(.assets/6.%20Object/20150427180107336)]
看来**dict
存储在(char *)obj + dictoffset
这个位置,它存储在所属的PyObject
的内存的后面。
虽然PyObject
struct中没有__dict__
,但是__dict__
可以由PyObject
的内存地址和它的ob_type
来确定,而且事实上它就在PyObject
的内存空间的后面,所以可以将__dict__
视为PyObject
的成员,实际上Python的解释器也是这么处理的。
__doc__
__doc__
也是魔术属性,但是它和普通变量一样存储在__dict__
中。
源码中对__doc__
的设置。
static int
type_set_doc(PyTypeObject *type, PyObject *value, void *context)
{
if (!check_set_special_type_attr(type, value, "__doc__"))
return -1;
PyType_Modified(type);
return _PyDict_SetItemId(type->tp_dict, &PyId___doc__, value);
}
不止普通对象有魔术属性,type
对象也有特有的魔术属性,就不一一举例的。
普通属性
证明一下普通属性是存储在__dict__
中的。
查看文档,普通属性的定义函数如下
c:function:: int PyObject_SetAttr(PyObject *o, PyObject *attr_name, PyObject *v)
Set the value of the attribute named *attr_name*, for object *o*, to the value
*v*. Raise an exception and return ``-1`` on failure;
return ``0`` on success. This is the equivalent of the Python statement
``o.attr_name = v``.
If *v* is ``NULL``, the attribute is deleted, however this feature is
deprecated in favour of using :c:func:`PyObject_DelAttr`.
看来这个c函数就是对象的属性定义过程,跟踪一下:
int
PyObject_SetAttr(PyObject *v, PyObject *name, PyObject *value)
{
PyTypeObject *tp = Py_TYPE(v);
int err;
if (!PyUnicode_Check(name)) {
PyErr_Format(PyExc_TypeError,
"attribute name must be string, not '%.200s'",
Py_TYPE(name)->tp_name);
return -1;
}
Py_INCREF(name);
PyUnicode_InternInPlace(&name);
if (tp->tp_setattro != NULL) {//调用typeobject的tp_setattro
err = (*tp->tp_setattro)(v, name, value);
Py_DECREF(name);
return err;
}
if (tp->tp_setattr != NULL) {
const char *name_str = PyUnicode_AsUTF8(name);
if (name_str == NULL) {
Py_DECREF(name);
return -1;
}
err = (*tp->tp_setattr)(v, (char *)name_str, value);
Py_DECREF(name);
return err;
}
...
return -1;
}
//从
//PyTypeObject->tp_setattro其实就是下面的函数
int
PyObject_GenericSetAttr(PyObject *obj, PyObject *name, PyObject *value)
{
return _PyObject_GenericSetAttrWithDict(obj, name, value, NULL);
}
int
_PyObject_GenericSetAttrWithDict(PyObject *obj, PyObject *name,
PyObject *value, PyObject *dict)
{
PyTypeObject *tp = Py_TYPE(obj);
...
if (dict == NULL) {
dictptr = _PyObject_GetDictPtr(obj);//获取__dict__
if (dictptr == NULL) {...}
res = _PyObjectDict_SetItem(tp, dictptr, name, value);//写入__dict__
}else {...}
if (res < 0 && PyErr_ExceptionMatches(PyExc_KeyError))
PyErr_SetObject(PyExc_AttributeError, name);
done:
Py_XDECREF(descr);
Py_DECREF(name);
return res;
}
从代码中看出,PyObject的属性定义过程简化如下
o.attr_name = v
获取type
根据type获取__dict__
将属性写入__dict__
PyObject_SetAttr(o, attr_name,v):
t = type(o) # 获取类
t.PyObject_GenericSetAttr(o, attr_name,v): # 调用类的__setattr__()
_PyObject_GenericSetAttrWithDict(o, attr_name,v, NULL): # __setattr__()中调用
tp = type(o)
dict = _PyObject_GetDictPtr(o)
_PyObjectDict_SetItem(tp, dict, attr_name, v) # 写入__dict__
所以对象的属性的定义都写入__dict__
中。
所以如果没有__dict__
就不能为对象添加属性(比如list,tuple等类型就没有__dict__
,不能添加属性,可以去测试一下)。