Python中魔术属性

普通属性和魔术属性

普通属性都存储在__dict__属性中,

而魔术属性由Python决定存储位置,有的魔术属性有特殊的存储方式,也可以跟普通属性一样存储在__dict__中。

重新赋值的魔术方法都存储在__dict__中。

魔术属性和普通属性的区别是魔术属性在解释器中有专门的处理函数,

比如__dict___PyObject_GetDictPtr等,__doc__type_set_doc等为这些魔术属性进行特别的处理。

而普通属性是使用通用的函数处理。

我们可以用Python的源码中查找它们的定义和使用流程。

object和type源码

先看看objecttype这两个最重要的基类的源码。

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__,不能添加属性,可以去测试一下)。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值