1、对象的引用计数

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

  1. from ctypes import *
  2.  
  3. class PyObject(Structure):
  4. _fields_ = [("refcnt", c_size_t),
  5. ("typeid", c_void_p)]
  6.  
  7. a = "this is a string"
  8.  
  9. # 通过id(a)可以获得对象a的内存地址,而PyObject.from_address()可以将
  10. # 指定的内存地址的内容转换为一个PyObject对象。通过此PyObject对象obj_a
  11. # 可以访问对象a的结构体中的内容。
  12. obj_a = PyObject.from_address(id(a))
  13. print(obj_a.refcnt)
  14. b = [a]*10
  15. print(obj_a.refcnt)

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

2、对象的类型

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

  1. print(obj_a.typeid)
  2. print(id(type(a)))
  3. print(id(str))

1825992064

1825992064

1825992064

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

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

1825980464

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

3、元类的特殊性质

  1. print(id(type))
  2. type_obj = PyObject.from_address(id(type))
  3. print(type_obj.typeid)

1825980464

1825980464

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

4、不可变对象值存储

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

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

28

24

5.1、扁平序列的创建

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

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

  1. # 在定义长度不固定的字段时,使用长度为0的数组定义一个不占内存的
  2. # 伪字段_val。create_var_object()用来创建大小不固定的结构体对象
  3. class PyVarObject(PyObject):
  4. _fields_ = [("size", c_size_t)]
  5.  
  6. class PyStr(PyVarObject):
  7. _fields_ = [("hash", c_long),
  8. ("state", c_int),
  9. ("_val", c_char*0)]
  10.  
  11. class PyLong(PyVarObject):
  12. _fields_ = [("_val", c_uint16*0)]
  13.  
  14. def create_var_object(struct, obj):
  15. inner_type = None
  16. for name, t in struct._fields_:
  17. # 首先搜索名为_val的字段,并将其类型保存到inner_type中
  18. if name == "_val":
  19. inner_type = t._type_
  20. print(inner_type)
  21. if inner_type is not None:
  22. # 然后创建一个PyVarObject结构体读取obj对象中的size字段
  23. tmp = PyVarObject.from_address(id(obj))
  24. size = tmp.size
  25. # 再通过size字段的大小创建一个对应的Inner结构体类,它可
  26. # 以从struct继承,因为struct中的_val字段不占据内存。
  27. class Inner(struct):
  28. _fields_ = [("val", inner_type*size)]
  29. Inner.__name__ = struct.__name__
  30. struct = Inner
  31. return struct.from_address(id(obj))
  32.  
  33. s = 'asdfgh'
  34. s_obj = create_var_object(PyStr, s)
  35. s_obj.size
  1. <class 'ctypes.c_char'>
  1. 6

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

5.2、(伪)长整型数字

  1. l = 0x1234567890abcd
  2. l_obj = create_var_object(PyLong, l)
  3. print(l_obj.size)
  4. val = list(l_obj.val)
  5. print(val)

<class 'ctypes.c_ushort'>

2

[43981, 14480]

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

6、List列表类型

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

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

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

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

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

删除元素原理一致,

  1. def test_list2():
  2. alist = [1] * 10000
  3. alist_obj = PyList.from_address(id(alist))
  4.  
  5. alist_obj.print_field()
  6. del alist[10:]
  7. alist_obj.print_field()
  8. test_list2()
  1. 10000 10000 <cparam 'P' (000001E0A24E63B0)>
  2. 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. Docker入门5------生产力工具docker-compose

    参考: https://www.cnblogs.com/neptunemoon/p/6512121.html 待续 安装参见docker-compose官网: https://github.com/d ...

  2. REST风格的5条关键原则

    REST风格的5条关键原则包括: (1)网络上的所有事物都被抽象为资源. (2)每个资源对应一个唯一的资源标识. (3)通过通用的连接件接口对资源进行操作. (4)对资源的各种操作不会改变资源标识. ...

  3. git常用操作命令使用说明

    设置用户名和邮箱 git config --global user.email 'xxx' git config --global user.name 'xxx' 创建分支 git branch xx ...

  4. java框架之Quartz-任务调度&整合Spring

    准备 介绍 定时任务,无论是互联网公司还是传统的软件行业都是必不可少的.Quartz,它是好多优秀的定时任务开源框架的基础,使用它,我们可以使用最简单基础的配置来轻松的使用定时任务. Quartz 是 ...

  5. JavaBean-EL-JSTL-MVC

    JavaBean规范 类必须使用public修饰     必须保证有公共无参数构造器.  (一般就是可以通过反射轻松的创建对象)  包含了属性的操作(给属性赋值,获取属性值). JavaBean中的成 ...

  6. #WEB安全基础 : HTTP协议 | 0x1 TCP/IP通信

    TCP/IP是如何通信的呢? 请看图 用TCP/IP协议族通信时,会通过分层顺序与对方进行通信.发送端从应用层往下走,接受层从链路层往上走. 客户端为了浏览界面在应用层发送请求,为了方便传输在传输层的 ...

  7. unity UGUI UI跟随

    实现2dUI跟随游戏中角色的移动(应用于玩家名称,血条,称号) using UnityEngine; public class UI_Follow : MonoBehaviour { public C ...

  8. ZAmbIE [DDoS Attacks](DDOS攻击)

    在youtube上发现的一个视频 这是一个开源项目 git clone https://github.com/zanyarjamal/zambie.git chmod -R 777 zambie cd ...

  9. Unable to execute 'doFinal' with cipher instance

    今天项目启动后登录项目,突然爆出Unable to execute 'doFinal' with cipher instance错误.清除cookie登录测试,又不报错了,以前也见过类似问题,因为不影 ...

  10. 剑指offer(9)变态跳台阶

    题目描述 一只青蛙一次可以跳上1级台阶,也可以跳上2级……它也可以跳上n级.求该青蛙跳上一个n级的台阶总共有多少种跳法. 题目分析 根据上一个题目可以知道,青蛙只跳1或2可以得出是一个斐波那契问题,即 ...