1、对象的引用计数

从c代码分析可知,python所有对象的内存有着同样的起始结构:引用计数+类型信息,实际上这些信息在python本体重也是可以透过包来一窥一二的,

from ctypes import *

class PyObject(Structure):
_fields_ = [("refcnt", c_size_t),
("typeid", c_void_p)] a = "this is a string" # 通过id(a)可以获得对象a的内存地址,而PyObject.from_address()可以将
# 指定的内存地址的内容转换为一个PyObject对象。通过此PyObject对象obj_a
# 可以访问对象a的结构体中的内容。
obj_a = PyObject.from_address(id(a))
print(obj_a.refcnt)
b = [a]*10
print(obj_a.refcnt)

查看对象a的引用次数,原文中返回值1,实际返回2,可能是python3相对2的改动使得多引用一次,接下来创建一个列表,此列表中的每个元素都是对象a,因此此列表应用了它10次,所以引用次数变为了12。

2、对象的类型

查看对象a的类型对象的地址,它和id(type(a))相同,而由于对象a的类型为str,因此也就是id(str),

print(obj_a.typeid)
print(id(type(a)))
print(id(str))

1825992064

1825992064

1825992064

验证一下,从这个地址读取python对象试试,

obj_s = PyObject.from_address(id(str))
print(obj_s.typeid)

1825980464

指向新的地址,如果这个地址对应python的类型对象,那么它的类型应该是元类对象,我们尝试一下在源码解析一书上的知识。

3、元类的特殊性质

print(id(type))
type_obj = PyObject.from_address(id(type))
print(type_obj.typeid)

1825980464

1825980464

符合,str(对应字符串类)的类型、元类本身的类型的、元类本身,三者的地址都指向同一位置。

4、不可变对象值存储

这种对象除了有PyObject中的两个字段之外,还有一个val字段保存实际的值,两者占用空间也就不同,这种不可变对象的内存值部分与头信息是相连的,所以使用sys.getsizeof可以一并查到

import sys
print(sys.getsizeof(1))
print(sys.getsizeof(1.0))

28

24

5.1、扁平序列的创建

  • 指定创建元素类型(字符串为字符,数组为int或者float)
  • 获取目标对象长度(即几个数字,几个字符)
  • 有了这两个信息就可以划分内存创建对象了
    • 原数据就存储在内存中,我们指定结构体去赋予新格式,并加入新的对象头信息

模拟C语言创建扁平序列的过程,

# 在定义长度不固定的字段时,使用长度为0的数组定义一个不占内存的
# 伪字段_val。create_var_object()用来创建大小不固定的结构体对象
class PyVarObject(PyObject):
_fields_ = [("size", c_size_t)] class PyStr(PyVarObject):
_fields_ = [("hash", c_long),
("state", c_int),
("_val", c_char*0)] class PyLong(PyVarObject):
_fields_ = [("_val", c_uint16*0)] def create_var_object(struct, obj):
inner_type = None
for name, t in struct._fields_:
# 首先搜索名为_val的字段,并将其类型保存到inner_type中
if name == "_val":
inner_type = t._type_
print(inner_type)
if inner_type is not None:
# 然后创建一个PyVarObject结构体读取obj对象中的size字段
tmp = PyVarObject.from_address(id(obj))
size = tmp.size
# 再通过size字段的大小创建一个对应的Inner结构体类,它可
# 以从struct继承,因为struct中的_val字段不占据内存。
class Inner(struct):
_fields_ = [("val", inner_type*size)]
Inner.__name__ = struct.__name__
struct = Inner
return struct.from_address(id(obj)) s = 'asdfgh'
s_obj = create_var_object(PyStr, s)
s_obj.size
<class 'ctypes.c_char'>
6

由于只是模拟,我们使用一个新建的伪字符串结构去从真的字符串结构读取数据格式化解析,并创建新对象。

5.2、(伪)长整型数字

l = 0x1234567890abcd
l_obj = create_var_object(PyLong, l)
print(l_obj.size)
val = list(l_obj.val)
print(val)

<class 'ctypes.c_ushort'>

2

[43981, 14480]

不论是几进制,实际l的type就是int。这里面我们看到了,实际上我们使用了多个普通的整形数表达一个很大的值,长整型实际上是一个序列。

6、List列表类型

列表对象的长度是可变的,因此不能采用字符串那样的结构体,

而是使用了一个指针字段items指向可变长度的数组,而这个数组本身是一个指向PyObject的指针。allocated字段表示这个指针数组的长度,而size字段表示指针数组中已经使用的元素个数,即列表的长度。列表结构体本身的大小是固定的。

class PyList(PyVarObject):
# item字段是指针数组,这里表现为二级指针
_fields_ = [("items", POINTER(POINTER(PyObject))),
("allocated", c_size_t)] def print_field(self):
# size字段表示指针数组中已经使用的元素个数
# allocated字段表示这个指针数组的长度
# 指针字段items指向可变长度的数组
print (self.size, self.allocated, byref(self.items[0])) # 测试一下添加元素
def test_list():
alist = [1,2.3,"abc"]
alist_obj = PyList.from_address(id(alist)) for x in range(10):
alist_obj.print_field()
alist.append(x)
test_list()

一开始列表的长度和其指针数组的长度都是3,即列表处于饱和状态。因此向列表中添加新元素时,需要重新分配指针数组,因此指针数组的长度变为了7,而地址也发生了变化。这时列表的长度为4,因此指针数组中还有3个空位保存新的元素。由于每次重新分配指针数组时,都会预分配一些额外空间,因此往列表中添加元素的平均时间复杂度为O(1),

3 3 <cparam 'P' (000001E0A1ABB330)>
4 7 <cparam 'P' (000001E09E0B14D0)>
5 7 <cparam 'P' (000001E09E0B14D0)>
6 7 <cparam 'P' (000001E09E0B14D0)>
7 7 <cparam 'P' (000001E09E0B14D0)>
8 12 <cparam 'P' (000001E0A0832AF0)>
9 12 <cparam 'P' (000001E0A0832AF0)>
10 12 <cparam 'P' (000001E0A0832AF0)>
11 12 <cparam 'P' (000001E0A0832AF0)>
12 12 <cparam 'P' (000001E0A0832AF0)>

删除元素原理一致,

def test_list2():
alist = [1] * 10000
alist_obj = PyList.from_address(id(alist)) alist_obj.print_field()
del alist[10:]
alist_obj.print_field()
test_list2()
10000 10000 <cparam 'P' (000001E0A24E63B0)>
10 17 <cparam 'P' (000001E0A24E63B0)>

『Python』源码解析_从ctype模块理解对象的更多相关文章

  1. 『Python』源码解析_源码文件介绍

    本篇代码针对2.X版本,与3.X版本细节不尽相同,由于两者架构差别不大加之本人能力有限,所以就使用2.X体验python的底层原理了. 一.主要文件夹内容 Include :该目录下包含了Python ...

  2. 神经网络中 BP 算法的原理与 Python 实现源码解析

    最近这段时间系统性的学习了 BP 算法后写下了这篇学习笔记,因为能力有限,若有明显错误,还请指正. 什么是梯度下降和链式求导法则 假设我们有一个函数 J(w),如下图所示. 梯度下降示意图 现在,我们 ...

  3. Python简单源码解析

    主要为一些简单的源代码的解析以及一些方法的理解. 说明:这些文件都不是我写的,详情可参考Github上的内容. 批量修改文件类型 def batch_rename(work_dir, old_ext, ...

  4. jquery 1.7.2源码解析(二)构造jquery对象

    构造jquery对象 jQuery对象是一个类数组对象. 一)构造函数jQuery() 构造函数的7种用法: 1.jQuery(selector [, context ]) 传入字符串参数:检查该字符 ...

  5. 【Spring源码解析】—— 结合SpringMVC过程理解IOC容器初始化

    关于IOC容器的初始化,结合之前SpringMVC的demo,对其过程进行一个相对详细的梳理,主要分为几个部分: 一.IOC的初始化过程,结合代码和debug过程重点说明 1. 为什么要debug? ...

  6. jQuery 源码解析(三十) 动画模块 $.animate()详解

    jQuery的动画模块提供了包括隐藏显示动画.渐显渐隐动画.滑入划出动画,同时还支持构造复杂自定义动画,动画模块用到了之前讲解过的很多其它很多模块,例如队列.事件等等, $.animate()的用法如 ...

  7. jquery源码解析:val方法和valHooks对象详解

    这一课,我们将讲解val方法,以及对value属性的兼容性处理,jQuery中通过valHooks对象来处理. 首先,我们先来看下val方法的使用: $("#input1").va ...

  8. jQuery 源码解析(八) 异步队列模块 Callbacks 回调函数详解

    异步队列用于实现异步任务和回调函数的解耦,为ajax模块.队列模块.ready事件提供基础功能,包含三个部分:Query.Callbacks(flags).jQuery.Deferred(funct) ...

  9. 【Spring源码解析】—— 委派模式的理解和使用

    一.什么是委派模式 委派模式,是指什么呢?从字面含义理解,委派就是委托安排的意思,委派模式就是在做具体某件事情的过程中,交给其他人来做,这个事件就是在我的完整链路上的一部分,但是复杂度较高的情况下或者 ...

随机推荐

  1. 《Redis 优化》

    一:管道技术 - 由于 redis 和 客户端是使用 TCP 连接的,那么在使用中就会产生往返耗时. - 虽然可能单条影响并不大,但是如果执行较多的命令会对性能产生影响. - 使用管道原理和 keep ...

  2. 16.3-uC/OS-III同步 (事件标志组实验)

    事件标志组,顾名思义,就是若干个事件标志的组合,代表若干个事件是否发生,通常用于集合两个或两个以上事件的状态 . 1.如果想要使用事件标志组,就必须事先使能事件标志组.消息队列的使能位于“os_cfg ...

  3. 浅谈Java中的关键字

    谈到final关键字,想必很多人都不陌生,在使用匿名内部类的时候可能会经常用到final关键字.另外,Java中的String类就是一个final类,那么今天我们就来了解final这个关键字的用法. ...

  4. PHP日常模拟业务的小工具

    随机生成姓名 public function getChar($num=2) // $num为生成汉字的数量 { $first = array('赵','钱','孙','李','周','吴','郑', ...

  5. break,continue的区别

    break 终止循环, continue 跳出本次循环,进入下一次循环 username = 'Loker'passwd = '123456' for i in range(3): user = in ...

  6. js 判断浏览器型号

    <html><head> <meta http-equiv="Content-Type" content="text/html;charse ...

  7. 让sublime可以和visual studio一样自动在运算符前后添加空格的插件

    用过vs的人都知道,vs会自动在代码中运算符的前后加空格,比如 i=1; 换行后会自动变成i = 1; 开始觉得这个挺烦的,后来习惯了,发现这个功能还是挺好的,然代码更清晰. 最近换了sublimet ...

  8. Python之猴子补丁

    1.在运行时,对属性,方法,函数等进行动态替换 2.其目的往往是为了通过替换,修改来增强,扩展原有代码的能力 #test2.py class Person: def get_score(self): ...

  9. div “下沉”

    最近在做一个计算器,按键整体布局如下: Div2,div3 display属性设置为inline-block.三个div “容器”没添加任何元素时,布局是符合预想的.添加上按键后,布局变成下面这样了: ...

  10. Select input 两个元素的宽度高度跟设定值不一致的问题

    在做登录框的时候,需要一个select 元素作为账号输入,一个input作为密码输入框.在css 文件中,将这两个元素的position 设置为relative ,并且width 设置为100%.刷新 ...