『Python』源码解析_从ctype模块理解对象
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模块理解对象的更多相关文章
- 『Python』源码解析_源码文件介绍
本篇代码针对2.X版本,与3.X版本细节不尽相同,由于两者架构差别不大加之本人能力有限,所以就使用2.X体验python的底层原理了. 一.主要文件夹内容 Include :该目录下包含了Python ...
- 神经网络中 BP 算法的原理与 Python 实现源码解析
最近这段时间系统性的学习了 BP 算法后写下了这篇学习笔记,因为能力有限,若有明显错误,还请指正. 什么是梯度下降和链式求导法则 假设我们有一个函数 J(w),如下图所示. 梯度下降示意图 现在,我们 ...
- Python简单源码解析
主要为一些简单的源代码的解析以及一些方法的理解. 说明:这些文件都不是我写的,详情可参考Github上的内容. 批量修改文件类型 def batch_rename(work_dir, old_ext, ...
- jquery 1.7.2源码解析(二)构造jquery对象
构造jquery对象 jQuery对象是一个类数组对象. 一)构造函数jQuery() 构造函数的7种用法: 1.jQuery(selector [, context ]) 传入字符串参数:检查该字符 ...
- 【Spring源码解析】—— 结合SpringMVC过程理解IOC容器初始化
关于IOC容器的初始化,结合之前SpringMVC的demo,对其过程进行一个相对详细的梳理,主要分为几个部分: 一.IOC的初始化过程,结合代码和debug过程重点说明 1. 为什么要debug? ...
- jQuery 源码解析(三十) 动画模块 $.animate()详解
jQuery的动画模块提供了包括隐藏显示动画.渐显渐隐动画.滑入划出动画,同时还支持构造复杂自定义动画,动画模块用到了之前讲解过的很多其它很多模块,例如队列.事件等等, $.animate()的用法如 ...
- jquery源码解析:val方法和valHooks对象详解
这一课,我们将讲解val方法,以及对value属性的兼容性处理,jQuery中通过valHooks对象来处理. 首先,我们先来看下val方法的使用: $("#input1").va ...
- jQuery 源码解析(八) 异步队列模块 Callbacks 回调函数详解
异步队列用于实现异步任务和回调函数的解耦,为ajax模块.队列模块.ready事件提供基础功能,包含三个部分:Query.Callbacks(flags).jQuery.Deferred(funct) ...
- 【Spring源码解析】—— 委派模式的理解和使用
一.什么是委派模式 委派模式,是指什么呢?从字面含义理解,委派就是委托安排的意思,委派模式就是在做具体某件事情的过程中,交给其他人来做,这个事件就是在我的完整链路上的一部分,但是复杂度较高的情况下或者 ...
随机推荐
- 微信小程序http 400问题
在v0.14.140900版本的wechat小程序开发工具中做网络请求,直接使用微信的网络请求代码debug过程中发生了400 (Bad request)错误. wx.request({ url: ' ...
- 16.2-uC/OS-III同步 (事件标志组)
事件标志组 1.当任务要与多个事件同步时可以使用事件标志.若其中的任意一个事件发生时任务被就绪, 叫做逻辑或(OR).若所有的事件都发生时任务被就绪,叫做逻辑与( AND). 2.用户可以创建任意个事 ...
- Spring 学习教程(一):浅谈对Spring IOC以及DI的理解
一.个人对IoC(控制反转)和DI(依赖注入)的理解我们平时在开发java web程序的时候,每个对象在需要使用它的合作对象时,自己都要将它要合作对象创建出来(比如 new 对象),这个合作对象是由自 ...
- GDB查看堆栈局部变量
GDB查看堆栈局部变量 “参数从右到左入栈”,“局部变量在栈上分配空间”,听的耳朵都起茧子了.最近做项目涉及C和汇编互相调用,写代码的时候才发现没真正弄明白.自己写了个最简单的函数,用gdb跟踪了调用 ...
- [js]js代码执行顺序/全局&私有变量/作用域链/闭包
js代码执行顺序/全局&私有变量/作用域链 <script> /* 浏览器提供全局作用域(js执行环境)(栈内存) --> 1,预解释(仅带var的可以): 声明+定义 1. ...
- #WEB安全基础 : HTTP协议 | 0x8 HTTP的Cookie技术
说道Cookie,你喜欢吃饼干吗? 这里的Cookie不是饼干=_= HTTP不对请求和响应的通信状态进行保存,所以被称为无状态协议,为了保持状态和协议功能引入了Cookie技术 Cookie技术在请 ...
- windows程序设计 创建一个新的窗口
#include <windows.h> LRESULT CALLBACK myProc(HWND, UINT, WPARAM, LPARAM); int WINAPI WinMain(H ...
- [openjudge-搜索]湖的深度
题目描述 描述 一个湖用 R x C (1 ≤ R ≤ 50; 1 ≤ C ≤ 50) 的网格表示.格点上的非负整数 D_rc (0 ≤ D_rc ≤ 1,000,000)表示该位置的深度.整数0表示 ...
- Mysql报错:Packet for query is too large (1121604 > 1048576).You can change this value on the server by setting the max_allowed_packet variable
看错误信息,发现1048576个字节,正好是1*1024*1024byte,也就是1Mb. 这正是mysql默认的max_allowed_packet值. 使用sql语句: show VARIABLE ...
- Access restriction: The type 'Unsafe' is not API
错误:Access restriction: The type 'Unsafe' is not API Eclipse中有一种叫做存取限制的机制,来防止你错误使用那些非共享的API.通常来说,Ecli ...