一,先谈谈不使用__slots__,通常定义完class,给实例绑定属性与方法,对另一个实例没有作用的

from types import MethodTypeclass Hero (object):passdef set_elem(self,elem):self.elem = elemhero = Hero()
hero.name = 'Ani'hero.set_elem = MethodType(set_elem, hero)            # 给实例绑定一个方法hero.set_elem('magic')
print(hero.name,hero.elem)
结果:
Ani magic
herotester = Hero()
herotester.set_elem('tester')                             #创建一个新的实例调用set_elem
结果:
AttributeError: type object 'herotester' has no attribute 'set_elem'

这时候可以给class绑定方法

Hero.set_elem = set_elem

执行上一步,发现所有实例均可调用

herotester = Hero()
Hero.set_elem('tester')                             #创建一个新的实例调用set_elem
print(herotester.elem)
结果:
Ani magic
tester

通常情况下,上面的set_elem方法可以直接定义在class中,但动态绑定允许我们在程序运行的过程中动态给class加上功能,静态语言却很难实现

二,回到正文,对于Hero类在动态语言中可以随时随意的修改其属性及方法,所以为了达到限制目的,引入__slots__,使得上述的案例动态绑定对应的属性或者方法

class Hero(object):__slots__ = (‘name’,‘elem’,‘set_zhuangbei’)                           #tuple定义绑定的属性名称

再进行对方法和属性的测试

hero.set_elem = MethodType(set_elem, hero) # 给实例绑定一个方法
此时报错:
AttributeError: 'Hero' object has no attribute 'set_elem'                             #方法报错
herotester = Hero()
Herotester.sex = woman
此时报错
AttributeError: 'Hero' object has no attribute 'sex'                                 #属性报错

而对__slots__添加方法set_elem则可以

class Hero(object):__slots__ = ('name','elem','set_elem')def set_elem(self,elem):self.elem = elemhero.set_elem('magic')
print(hero.elem)

**但是此处有一个特例,如果set_elem方法中不在__slots__中的属性,那么依然会报错如下,如下对set_elem稍加修改

class Hero(object):__slots__ = ('name','elem','set_elem')def set_elem(self,elemm):self.elemm = elemm

结果:

AttributeError: 'Hero' object has no attribute 'elemm'

由此可见__slots__做了一个绑定类的方法(方法里的属性也同样做了限制)与属性的限制
同时注意,在__init__方法中定义的属性都是动态的,都受到__slots__的限制。在__init__ 等方法的执行过程中,定义的属性跟终端通过实例增加属性是一样的,都是动态的。
在终端通过实例增加方法,受到__slots__的限制,但是在类的声明中通过def实现的方法是不受__slots__限制的,因为在类的声明中实现的方法是静态的。

三,如果父类做了__slots__的限制,而子类没有进行限制,那么子类依旧可以绑定任意的方法及属性,如下:

class Dog(Hero):passdog = Dog()
dog.fav = 'eat'
print(dog.fav)

可见父类__slots__定义的属性仅对当前类起作用,对继承的子类不起作用
但是如果子类也加上了__slots__,那么子类此时不仅使用自己的__slots__,同时也继承了父类的__slots__

见如下例:

class Dog(Hero):__slots__ = ('fav','run')dog = Dog()
dog.name = 'hashiqi'
print(dog.name)
dog.fav = 'eat'
dog.color = 'blue'

结果:

AttributeError: 'Dog' object has no attribute 'color'
hashiqi

四,浅谈__slots__的好处及源码剖析

(1)更省内存。 ————更省内存是因为实例的属性不以字典的形式存储,而是以更紧凑的格式。
(2)访问属性更高效。 ————更高效是因为实例在做属性查找的时候,节省了一次hash查找,改为以计算属性内存的偏移量直接读写内存。

定义类时、创建实例为其分配内存时、以及从实例访问属性时分别讨论__slots__的影响
(1)定义类
typeobject.c:

static PyObject *
type_new(PyTypeObject *metatype, PyObject *args, PyObject *kwds)
{.../* Check for a __slots__ sequence variable in dict, and count it */slots = PyDict_GetItemString(dict, "__slots__");nslots = 0;if (slots == NULL) {/* 类定义中没有__slots__,不需要关注 */}else {/* Have slots *//* Make it into a tuple */if (PyString_Check(slots) || PyUnicode_Check(slots))slots = PyTuple_Pack(1, slots);elseslots = PySequence_Tuple(slots);if (slots == NULL) {Py_DECREF(bases);return NULL;}assert(PyTuple_Check(slots));/* Copy slots into a list, mangle names and sort them.Sorted names are needed for __class__ assignment.Convert them back to tuple at the end.*/newslots = PyList_New(nslots - add_dict - add_weak);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) {Py_DECREF(newslots);goto bad_slots;}PyList_SET_ITEM(newslots, j, tmp);j++;}nslots = j;Py_DECREF(slots);if (PyList_Sort(newslots) == -1) {Py_DECREF(bases);Py_DECREF(newslots);return NULL;}slots = PyList_AsTuple(newslots);Py_DECREF(newslots);if (slots == NULL) {Py_DECREF(bases);return NULL;}}/* Allocate the type object *//* 为类对象申请内存,这里分配内存时也考虑了存储slots需要的内存 */type = (PyTypeObject *)metatype->tp_alloc(metatype, nslots);if (type == NULL) {Py_XDECREF(slots);Py_DECREF(bases);return NULL;}/* Add descriptors for custom slots from __slots__, or for __dict__ *//* 将slots的数据作为member存储在类对象上,后续将会根据这个member创建具体的descriptior* 而实际上读写这个属性都是通过descriptior实现的*/mp = PyHeapType_GET_MEMBERS(et);slotoffset = base->tp_basicsize;if (slots != NULL) {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;/* __dict__ and __weakref__ are already filtered out */assert(strcmp(mp->name, "__dict__") != 0);assert(strcmp(mp->name, "__weakref__") != 0);slotoffset += sizeof(PyObject *);}}/* 类的type->tp_basicsize这个值描述了实例所占内存的大小(当然只是内存的一部分)* 而从上面的代码可以看出,slotoffset这个值包含了nslots个指针大小。没错!这个指针就是实际存储属性用的 * 因此slots是直接存储在实例内存上面的,而属性的具体位置的偏移值信息则以member存储在类对象上*/type->tp_basicsize = slotoffset;type->tp_itemsize = base->tp_itemsize;type->tp_members = PyHeapType_GET_MEMBERS(et);/* Always override allocation strategy to use regular heap */type->tp_alloc = PyType_GenericAlloc;/* 调用PyType_Ready这个函数时会为类身上的每个member创建一个descriptor* 当实例访问属性时,会需要借助这个descriptor的力量:P*/if (PyType_Ready(type) < 0) {Py_DECREF(type);return NULL;}return (PyObject *)type;
}

当我们定义一个类的时候,最后会调用到上面type_new这个函数。由于只关注slots,因此我省略掉了一部分的代码。可以看出,如果有定义slots,那么会将其信息以member的形式存储在类的身上。观察初始化member的代码,可以发现关于访问属性的最重要的两个数据都在其中,一个是属性的内存位置,由相对于实例的偏移值mp->offset描述。通过这个偏移值,我们能拿到属性数据在内存起始地址,但却不知道如何解释这块内存,因此还需要一个类型信息,这个信息由mp->type来补充。

剩下的工作便是在调用函数PyType_Ready时,根据member中存储的信息,创建出执行访问操作的descriptor对象。

int
PyType_Ready(PyTypeObject *type)
{/* Add type-specific descriptors to tp_dict */if (type->tp_members != NULL) {if (add_members(type, type->tp_members) < 0)goto error;}return 0;error:type->tp_flags &= ~Py_TPFLAGS_READYING;return -1;
}static int
add_members(PyTypeObject *type, PyMemberDef *memb)
{PyObject *dict = type->tp_dict;for (; memb->name != NULL; memb++) {PyObject *descr;if (PyDict_GetItemString(dict, memb->name))continue;descr = PyDescr_NewMember(type, memb);if (descr == NULL)return -1;if (PyDict_SetItemString(dict, memb->name, descr) < 0) {Py_DECREF(descr);return -1;}Py_DECREF(descr);}return 0;
}

同样的,省略了很多其它不相关的代码。可以看出,最终根据member创建出的descriptor是存储在type对象上的tp_dict中的。

(2)创建实例
当创建一个类的实例时,会为其分配内存。如果这个类定义了slots,那么会申请更多的内存,slots定义的属性便是存储在这部分内存中。直接看为实例申请内存的代码:

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)(void)PyObject_INIT(obj, type);else(void) PyObject_INIT_VAR((PyVarObject *)obj, type, nitems);if (PyType_IS_GC(type))_PyObject_GC_TRACK(obj);return obj;
}#define _PyObject_VAR_SIZE(typeobj, nitems)     \(size_t)                                    \( ( (typeobj)->tp_basicsize +               \(nitems)*(typeobj)->tp_itemsize +       \(SIZEOF_VOID_P - 1)                     \) & ~(SIZEOF_VOID_P - 1)                  \)

从代码可知,实例的内存大小与其type对象的tp_basicsize是相关联的。回看之前定义类时的type_new函数,会发现tp_basicsize这个值已经是包含了slots所需的内存了(详见计算member偏移值那部分代码)。type_new为slots中的每一项都分配一个指针长度的内存,而日后实例的属性便是存储在这个位置上。这也正是slots更省内存的原因!

(3)访问属性
最后来看从实例上访问slots的属性是怎样的,以读属性的值为例

/* Generic GetAttr functions - put these in your tp_[gs]etattro slot */PyObject *
_PyObject_GenericGetAttrWithDict(PyObject *obj, PyObject *name, PyObject *dict)
{PyTypeObject *tp = Py_TYPE(obj);PyObject *descr = NULL;PyObject *res = NULL;descrgetfunc f;Py_ssize_t dictoffset;PyObject **dictptr;
if (tp->tp_dict == NULL) {if (PyType_Ready(tp) < 0)goto done;
}descr = _PyType_Lookup(tp, name);Py_XINCREF(descr);f = NULL;
if (descr != NULL &&PyType_HasFeature(descr->ob_type, Py_TPFLAGS_HAVE_CLASS)) {f = descr->ob_type->tp_descr_get;if (f != NULL && PyDescr_IsData(descr)) {res = f(descr, obj, (PyObject *)obj->ob_type);Py_DECREF(descr);goto done;}
}if (dict == NULL) {/* Inline _PyObject_GetDictPtr */dictoffset = tp->tp_dictoffset;if (dictoffset != 0) {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 = *dictptr;}
}
if (dict != NULL) {Py_INCREF(dict);res = PyDict_GetItem(dict, name);if (res != NULL) {Py_INCREF(res);Py_XDECREF(descr);Py_DECREF(dict);goto done;}Py_DECREF(dict);
}if (f != NULL) {res = f(descr, obj, (PyObject *)Py_TYPE(obj));Py_DECREF(descr);goto done;
}if (descr != NULL) {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;
}

当从实例身上访问一个属性时,首先尝试从类对象的tp_dict查找,是否存在对应的descriptor。若是(查找slots的属性正是如此),调用descriptor身上的tp_descr_get方法,并将方法的返回值作为这次属性查找的结果返回。

从中也可以看出,如果是访问正常的属性时,还要根据type对象的dictoffset偏移值找到实例的属性字典,然后再在这个字典中执行hash查找属性。这就是为什么定义了slots后属性查找理论上会更高效。

看看tp_descr_get方法长啥样:

PyTypeObject PyMemberDescr_Type = {PyVarObject_HEAD_INIT(&PyType_Type, 0)"member_descriptor",sizeof(PyMemberDescrObject),0,(destructor)descr_dealloc,                  /* tp_dealloc */0,                                          /* tp_print */0,                                          /* tp_getattr */0,                                          /* tp_setattr */0,                                          /* tp_compare */(reprfunc)member_repr,                      /* tp_repr */0,                                          /* tp_as_number */0,                                          /* tp_as_sequence */0,                                          /* tp_as_mapping */0,                                          /* tp_hash */0,                                          /* tp_call */0,                                          /* tp_str */PyObject_GenericGetAttr,                    /* tp_getattro */0,                                          /* tp_setattro */0,                                          /* tp_as_buffer */Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC, /* tp_flags */0,                                          /* tp_doc */descr_traverse,                             /* tp_traverse */0,                                          /* tp_clear */0,                                          /* tp_richcompare */0,                                          /* tp_weaklistoffset */0,                                          /* tp_iter */0,                                          /* tp_iternext */0,                                          /* tp_methods */descr_members,                              /* tp_members */member_getset,                              /* tp_getset */0,                                          /* tp_base */0,                                          /* tp_dict */(descrgetfunc)member_get,                   /* tp_descr_get */(descrsetfunc)member_set,                   /* tp_descr_set */
};static PyObject *
member_get(PyMemberDescrObject *descr, PyObject *obj, PyObject *type)
{PyObject *res;
if (descr_check((PyDescrObject *)descr, obj, &res))return res;
return PyMember_GetOne((char *)obj, descr->d_member);

}

原来最后是通过函数PyMember_GetOne来获取属性。好!继续深入:

PyObject *
PyMember_GetOne(const char *addr, PyMemberDef *l)
{PyObject *v;if ((l->flags & READ_RESTRICTED) &&PyEval_GetRestricted()) {PyErr_SetString(PyExc_RuntimeError, "restricted attribute");return NULL;}addr += l->offset;switch (l->type) {case T_BOOL:v = PyBool_FromLong(*(char*)addr);break;case T_BYTE:v = PyInt_FromLong(*(char*)addr);break;case T_UBYTE:v = PyLong_FromUnsignedLong(*(unsigned char*)addr);break;case T_SHORT:v = PyInt_FromLong(*(short*)addr);break;case T_USHORT:v = PyLong_FromUnsignedLong(*(unsigned short*)addr);break;case T_INT:v = PyInt_FromLong(*(int*)addr);break;case T_UINT:v = PyLong_FromUnsignedLong(*(unsigned int*)addr);break;case T_LONG:v = PyInt_FromLong(*(long*)addr);break;case T_ULONG:v = PyLong_FromUnsignedLong(*(unsigned long*)addr);break;case T_PYSSIZET:v = PyInt_FromSsize_t(*(Py_ssize_t*)addr);break;case T_FLOAT:v = PyFloat_FromDouble((double)*(float*)addr);break;case T_DOUBLE:v = PyFloat_FromDouble(*(double*)addr);break;case T_STRING:if (*(char**)addr == NULL) {Py_INCREF(Py_None);v = Py_None;}elsev = PyString_FromString(*(char**)addr);break;case T_STRING_INPLACE:v = PyString_FromString((char*)addr);break;case T_CHAR:v = PyString_FromStringAndSize((char*)addr, 1);break;case T_OBJECT:v = *(PyObject **)addr;if (v == NULL)v = Py_None;Py_INCREF(v);break;case T_OBJECT_EX:/* slots对应的member->type是T_OBJECT_EX */v = *(PyObject **)addr;if (v == NULL)PyErr_SetString(PyExc_AttributeError, l->name);Py_XINCREF(v);break;
#ifdef HAVE_LONG_LONGcase T_LONGLONG:v = PyLong_FromLongLong(*(PY_LONG_LONG *)addr);break;case T_ULONGLONG:v = PyLong_FromUnsignedLongLong(*(unsigned PY_LONG_LONG *)addr);break;
#endif /* HAVE_LONG_LONG */default:PyErr_SetString(PyExc_SystemError, "bad memberdescr type");v = NULL;}return v;
}

根据member所记录的偏移值和类型,访问属性内存的代码了

python之__slots__相关推荐

  1. 通过Python的__slots__节省9GB内存

    我们之前提到过,Oyster.com基于Python的web服务器缓存了大量Python字典(dicts)(哈希表(hash tables))的静态内容.好啦,我们最近使用一行代码--在我们的Imag ...

  2. python的`__slots__`属性

    1. 引言 我们先看一个问题: 对象(通过类实例化后的对象)如何储存自己的属性? 1.1. 默认情况下:对象如何储存自己的属性 为了说明这个问题, 我们先在交互式模式中做一个简单的测试: >&g ...

  3. Python:__slots__()方法和@property方法

    1.__slots__ 1.python作为一个动态语言,可以在创建一个class类后,给类进行绑定属性和方法.但是当我们想要限制实例的属性和方法时怎么办?这个时候就可以用到__slots__()方法 ...

  4. python 使用__slots__来限制类的实例属性的数量

    正常情况下,当我们定义了一个class,创建了一个class的实例后,我们可以给该实例绑定任何属性和方法,这就是动态语言的灵活性.先定义class: class Student(object):pas ...

  5. python 之 __slots__

    一.背景 python 动态语言,可以在运行过程中,修改实例的属性和增删方法 Python有时我们只想使用固定的属性,不想任意绑定属性 __slots__ 作用 限制对象属性的添加,限制类的实例使用的 ...

  6. Python中__slots__的使用

    摘要 当一个类需要创建大量实例时,可以通过__slots__声明实例所需要的属性, 例如,class Foo(object): __slots__ = ['foo'].这样做带来以下优点: 更快的属性 ...

  7. Python: `__slots__`

    __slots__ ''' __slots__是什么:是一个类变量,变量值可以是列表,元祖,或者可迭代对象,也可以是一个字符串(意味着所有实例只有一个数据属性) 引子:使用点来访问属性本质就是在访问类 ...

  8. Python 面向对象 —— __slots__ 与 @property(python 下的 getter、setter 方法)

    1. 使用 __slots__ 限制 class 的属性 class Student(object):__slots__ = {'name', 'age'}# 集合# 也只允许绑定 name,age ...

  9. python基础——使用__slots__

    python基础--使用__slots__ 正常情况下,当我们定义了一个class,创建了一个class的实例后,我们可以给该实例绑定任何属性和方法,这就是动态语言的灵活性.先定义class: cla ...

最新文章

  1. ASP.NET WebAPI 11 参数验证
  2. UVA10780幂和阶乘
  3. 2.select_基础运用
  4. 第五章 Python数据结构
  5. boost::property_tree模块自定义 ptree 的 data_type 需要执行的操作
  6. android二分查找法简书,【PYTHON】二分查找算法
  7. IOS--CALayer的介绍及使用技巧
  8. Mac安装prometheus node_exporter
  9. mongodb java 地理位置_MongoDB的地理位置索引
  10. 软件项目管理 1.2.PMBOK与软件项目管理知识体系
  11. 【科研小技巧|知网】如何下载 PDF 格式的学位论文
  12. 抽签抽奖小程序小工具(jquery+html)只需浏览器运行xlsx.core.min.js
  13. 关于自编码器的核心点理解
  14. 如何解决win10语言栏消失变成空白问题
  15. 第二讲:双活灾备方案建设方法论
  16. 计算机组成原理之原码一位乘法过程
  17. 利用STM32CubeMX来设置精确到微妙(us)的定时器
  18. 微信公众号/企业微信插件用python的操作
  19. 【狂神说】 Redis 笔记分享
  20. canvas学习之五子棋规则法部分实现

热门文章

  1. 整数在计算机中的表示
  2. mysql以及DBeaver的使用
  3. java编写奇数偶数,java基础奇数偶数判断
  4. 集合2:方法和运算符—适用set/frozenset(上)
  5. Iptables 指南 1.1.19(中文版)(三)
  6. 工程伦理第五章习题答案
  7. 6.oop-类和对象
  8. 观《加缪诺奖获奖感言》有感
  9. might和could的区别用法_情态动词may与might用法
  10. 韦东山嵌入式开发板小结1