叙文相关描述主要是个人的认知和见解,如有不当之处,还望各位大佬指正。
小钟同学 | 文 【原创】| QQ:308711822 个人其他博客地址:
简书:https://www.jianshu.com/u/d6960089b087
掘金:https://juejin.cn/user/2963939079225608
个人公众号:

之前带一个初学编程语言的小弟的时候,就问过这么一个问题,什么是对象,(这个对象能处CP吗)? 今时今日对象对我回眸一笑,发现对这个对象才“百媚生”。
答:对象就是你媳妇呗。送你一首《凉凉》吧!哈哈
其实在编程语言里面,对象是一个比较抽象的概念。对于编程语言来说,它是一个相对独立的实体,众所周知,我们的python是动态型语言,意思就是说:在Python语言里,对象的类型和内存占用都是运行时确定的。虽然中间解析器会把我们的.py有编译成.pyc,但是它的动态性说明也说明了它是一个解释型的语言,程序运行时候,解释器也可以根据语法和右侧的操作数来决定新对象的类型。
在Python底层中不论是整数,字符串,甚至是其他容器级别的数据类型,都由C底层由一个叫struct PyObject结构体所封装。先前在内存一些介绍中,如图示:
有所介绍,从PyObject的结构体可以看出:类型是跟着堆内存中的对象走的。所以甚至于:我们的python中的类和方法等其实都是对象。如下示例:是对象都会分配堆内存地址:
class Person():
@classmethod
def run(cls):
pass
def cat(self):
pass
print(Person)
print(hex(id(Person)))
print(hex(id(Person.run())))
a = Person()
print(hex(id(a)))
print(hex(id(a.cat())))
输出的结果:
<class '__main__.Person'>
0x1f29dc65eb8
0x7fffa8487ce0
0x1f29e35b898
0x7fffa8487ce0
那接下来本节主要梳理一些模糊的概念知识点,也是我想重新梳理的几个点:
通常我们的一个程序启动后,程序都会向我们的系统征收一点内存来,支撑我们的程序的运行。而相关内存分配出于对内存优化机制,一般有两种方式进行分配:
图示通常是一个程序运行的时候相关内存分类的情况(PS:可能不同的言语不一样哟,这个还得再细究,不过C和C++应该就是按图示的)
Java的内存划分:
专业术语来说的话就是:
特性(类似弹夹一样):
其他描述:
由系统自动分配,它的速度较快。程序员是无法控制管理。
它存贮的是变量。(可以理解对象的引用)
栈内存存放的变量生命周期一旦结束就会被释放。
全局栈内存一般会随着程序的结束关闭和释放
若分配失败,则提示栈溢出错误。
私有栈内存:
(1)一般情况下,内部函数执行完成,形成的私有栈内存一般在作用域结束会被销毁掉。
(2)如果栈内存中的的地址(一般都是堆内存的地址)被私有作用域以外的事物占用,则无法立即释放(即:私有作用域中的私有变量等信息被保留下来了),但一般情况也会随程序销毁而销毁。
特性(类似弹夹一样):
其他描述:
PS:所以通常说的泄漏是哪个内存的泄漏?其实多数是指堆内存的泄漏!
签名说过,python对象在C的底层内部其实是分配一个对象。里面包含了几个重要的元素信息。
从PyObject的结构体可以看出:
转化为我们的PyObject对象:
由我们的刚刚的内存类型分类上来说,我们的所有创建的对象其实都是存在再堆内存里面。所以推导出了下面这张图:
此时我们的堆内存存在了,但是我们还没有给我们的对象分配一个变量啊?(就是分配栈内存地址,因为我们的栈内存存的是变量),所以我们的此时可以回答-堆和栈是怎么联系起来的呢?
从上图我们可以得到,其实当创建一个对象的时候,通常是需要一个变量去存贮我们的对象。没有变量进行引用的话多数情况下,可能就要等着‘被人道毁灭,回炉重造’!
所以此时我们的引出了另一个概念.叫做对象的引用?(类似C++和go中的指针),既然python中的变量都是指针,所以这个指针(内存地址)可以指向任何一个对象。
那什么是对象引用呐?
从上图看,第三步中:把堆内存地址存放在栈内存中,供变量a的引用。
PS:转化过来就是:Python变量持有Python对象在堆内存中的内存地址。
此时我们的a变量存的是我们对象‘小钟同学的’地址,所以,此时a也叫做也称为:对象(小钟同学的)的引用。
所以引用的本质就是:变量存储的就是对象的地址。a变量持有Python对象‘小钟同学’的引用.
专业术语总结的话:
由此我们可以总结出来:
解释器寻找引用值的过程:
从上面的分析关于堆内存和占内存的分析中,我们的可以得到,其实我们变量是存的是一个地址:所以:
a = ‘小钟同学’
b = a
print(id(a),id(b))得到的地址是一样
这里的 = 号 赋值 其实不是正确的应该是叫做(赋“地址”)
示例:
a= 'a'
b =a
print(id(a),a)
print(id(b),b)
# 创建了一个对象(申请了新的地址给a)
a= 'aaaaaaaaaaaaaa'
print(id(a),a)
print(id(b),b)
输出结果:
2260469193504 a
2260469193504 a
2260470942384 aaaaaaaaaaaaaa
2260469193504 a
总结:变量和对象是两个独立个体。
Python 在 堆内存(heap)中分配的对象分成两类:可变对象和不可变对象。
a= [1]
print(id(a),id(a[0]),a)
a= [1,2]
print(id(a),id(a[0]),a)
a[0]=89
print(id(a),id(a[0]),a)
文字解说上面的代码:
第1行代码:a= [1]:
创建了一个[]对象,此时会给[]分配一个堆内存地址
在[]对象里面,又创建了一个1的对象,此时会给1既(a[0]的引用)分配一个新的堆内存地址
a持有的是[]对象的地址,而不是a[0]对象的地址
所以最终打印出来的结果:print(id(a),id(a[0]),a) 输出结果:2443469767688 140736017195856 [1] 2443469767688 []对象的堆内存地址 140736017195856 a[0]对象1的堆内存地址
第3行代码:a= [1,2] :
创建了一个[1,2] 对象,此时会给[1,2] 分配一个堆内存地址,原来2443469767688 []对象的堆内存地址,在没有没有对应的变量进行绑定关联的,无家可归,只能被回收!引用计数变0,被系统返厂了!
新建[1,2]对象里面,又创建了一个2的对象,此时会给2既(a[1]的引用)分配一个新的堆内存地址,所以新增了一个地址了
a持有的是新创建[1,2]对象的地址,而不是原来2443469767688 []对象对象的地址了
但是我们的新创建出来的[1,2]对象的地址中原来的a[0]=1对象的地址还是存在,还是绑定在原来的 140736017195856 a[0]对象1的堆内存地址,所以它的地址不变。
所以最终打印出来的结果:print(id(a),id(a[0]),a) 输出结果:2443469731464 140736017195856 [1, 2] 2443469731464 [1,2] 对象的堆内存地址(a地址有变化) 140736017195856 a[0]对象1的堆内存地址(没有变化)
第5行代码:a[0]=89:
修改a[0]原来的对象1,现在变为了89,相当于创建了新的对象,1原来的对象没有引用了,会被回收。此时我们的[]对象里面的元素内容有变化了
所以最终打印出来的结果:print(id(a),id(a[0]),a) 输出结果:2443469731464 140736017198672 [89, 2] 2443469731464 [1,89] 对象的堆内存地址(a地址保持原来[]的2443469731464地址没有变化) 140736017195856 a[0]对象1的堆内存地址已经没了引用,新增了89的地址(有变化)
综上分析:
print('==================>')
print("str是不可变对象")
a = '小钟同学'
print('a引用地址',id(a))
b = '小钟同学'
print('b引用地址',id(b))
print('is判断引用是否一样:',a is b)
print('==判断是对象值内容是否一样',a ==b)
print('==================>')
b = '小钟同学+一百分(对象值改变了,会开辟新的空间地址)'
print('b引用地址',id(b))
print('is判断引用是否一样:',a is b)
print('==判断是对象值内容是否一样',a ==b)
第3行代码:a = '小钟同学':
创建了一个str对象,此时会给str分配一个堆内存地址
a持有的是str对象的地址的引用
所以最终打印出来的结果:print('a引用地址',id(a)) 输出结果:a引用地址 2532269760312 第5行代码:b = '小钟同学':
因为str对象是不可变的对象,它的堆内存的地址是不可变,此时会给str分配一个堆内存地址(还是原来‘小钟同学’的堆内存的地址)
b持有的是str对象的地址的引用(其实还是指向了'小钟同学'堆内存地址)
所以最终打印出来的结果:print('b引用地址',id(b)) 输出结果:b引用地址 2532269760312
所以最终打印出来的结果: print('is判断引用是否一样:',a is b) print('==判断是对象值内容是否一样',a ==b)
is判断引用是否一样: True ==判断的是对象值内容是否一样 True
第10行代码:b = '小钟同学+一百分(对象值改变了,会开辟新的空间地址)':
创建了一个新的str对象,此时会给新的str分配一个新的堆内存地址
b此时是新的str分配一个新的堆内存地址分配的一个栈内存地址(对象的引用)
原来就的‘小钟同学’对象的引用计数-1,但是还a持有它的引用,所以此时系统不会回收这个内存
所以最终打印出来的结果:print('b引用地址',id(b)) 输出结果:b引用地址 2545345760345(b的地址改变了 a的地址没有改变)
因为Python对象的分类,所以在涉及拷贝的时候,我们也需要区分开拷贝的对象。
关于浅拷贝几个需要关注的点,浅拷贝拷贝的对象分:可变和不可变
print('==================>')
print("str是不可变对象")
a = '小钟同学'
print('a引用地址',id(a))
import copy
b = copy.copy(a)
print('浅拷贝后a引用地址',id(a))
print('浅拷贝后b引用地址',id(b))
print('浅拷贝后a里面对象的值',a)
print('浅拷贝后b里面对象的值',b)
输出结果:
==================>
str是不可变对象
a引用地址 2241728814904
浅拷贝后a引用地址 2241728814904
浅拷贝后b引用地址 2241728814904
浅拷贝后a里面对象的值 小钟同学
浅拷贝后b里面对象的值 小钟同学
print('==================>')
print("lits是可变对象")
a = ['小钟同学']
print('a引用地址',id(a))
print('a[0]引用地址',id(a[0]))
import copy
b = copy.copy(a)
print('浅拷贝后a引用地址',id(a))
print('浅拷贝后a[0]里面引用地址',id(a[0]))
print('==================>')
print('浅拷贝后b引用地址(生成了的新地址)',id(b))
print('浅拷贝后b[0]里面引用地址(共享a[0])对象的引用的地址',id(b[0]))
print('浅拷贝后b里面对象的值',b)
输出结果:
==================>
lits是可变对象
a引用地址 1988972495880
a[0]引用地址 1988967407416
浅拷贝后a引用地址 1988972495880
浅拷贝后a[0]里面引用地址 1988967407416
==================>
浅拷贝后b引用地址(生成了的新地址) 1988999589128
浅拷贝后b[0]里面引用地址(共享a[0])对象的引用的地址 1988967407416
浅拷贝后b里面对象的值 ['小钟同学']
print('==================>')
print("str是不可变对象")
a = '小钟同学'
print('a引用地址',id(a))
import copy
b = copy.deepcopy(a)
print('深拷贝后a引用地址',id(a))
print('深拷贝后a对象的值',a)
print('深拷贝后b里面引用地址',id(b))
print('深拷贝后b对象的值',b)
输出的结果为:
==================>
str是不可变对象
a引用地址 2847099133752
深拷贝后a引用地址 2847099133752
深拷贝后a对象的值 小钟同学
深拷贝后b里面引用地址 2847099133752
深拷贝后b对象的值 小钟同学
意思就是:列表里面还有列表的话,产生的拷贝的,时候,所有的可变一样会生产新的地址 如:
print('==================>')
print("list是可变对象")
a = ['小钟同学',["内嵌列表的小钟同学"]]
print('深拷贝前---》a引用地址',id(a))
print('深拷贝前---》a引用的对象的值是:',a)
print('深拷贝前---》a[0]引用地址',id(a[0]))
print('深拷贝前---》a[0]引用的对象的值是:',a[0])
print("**重点关注内嵌的列表对象**")
print('深拷贝前---》a[1]引用地址',id(a[1]))
print('深拷贝前---》a[1]引用的对象的值是:',a[1])
import copy
b = copy.deepcopy(a)
print('==================深拷贝后====a状态==============>')
print('深拷贝后---》a引用地址',id(a))
print('深拷贝后---》a引用的对象的值是:',a)
print('深拷贝后---》a[0]引用地址',id(a[0]))
print('深拷贝后---》a[0]引用的对象的值是:',a[0])
print("**重点关注内嵌的列表对象**")
print('深拷贝后---》a[1]引用地址',id(a[1]))
print('深拷贝后---》a[1]引用的对象的值是:',a[1])
print('==================深拷贝后====b状态==============>')
print('深拷贝后---》b引用地址',id(b))
print('深拷贝后---》b引用的对象的值是:',b)
print('深拷贝后---》b[0]引用地址(因为是不可变对象的拷贝,地址保持不变)',id(b[0]))
print('深拷贝后---》b[0]引用的对象的值是:',b[0])
print("**重点关注内嵌的列表对象**")
print('深拷贝后---》b[1]引用地址',id(b[1]))
print('深拷贝后---》b[1]引用的对象的值是:',b[1])
print('==================深拷贝后==============>')
print('==================深拷贝后====对比:b[0]和a[0]=============>')
print(b[0] is a[0])
print('==================深拷贝后====对比:b[1]和a[1]=============>')
print(b[1] is a[1])
输出的结果为:
==================>
list是可变对象
深拷贝前---》a引用地址 2238894698248
深拷贝前---》a引用的对象的值是: ['小钟同学', ['内嵌列表的小钟同学']]
深拷贝前---》a[0]引用地址 2238889527584
深拷贝前---》a[0]引用的对象的值是: 小钟同学
**重点关注内嵌的列表对象**
深拷贝前---》a[1]引用地址 2238893966472
深拷贝前---》a[1]引用的对象的值是: ['内嵌列表的小钟同学']
==================深拷贝后====a状态==============>
深拷贝后---》a引用地址 2238894698248
深拷贝后---》a引用的对象的值是: ['小钟同学', ['内嵌列表的小钟同学']]
深拷贝后---》a[0]引用地址 2238889527584
深拷贝后---》a[0]引用的对象的值是: 小钟同学
**重点关注内嵌的列表对象**
深拷贝后---》a[1]引用地址 2238893966472
深拷贝后---》a[1]引用的对象的值是: ['内嵌列表的小钟同学']
==================深拷贝后====b状态==============>
深拷贝后---》b引用地址 2238921063944
深拷贝后---》b引用的对象的值是: ['小钟同学', ['内嵌列表的小钟同学']]
深拷贝后---》b[0]引用地址(因为是不可变对象的拷贝,地址保持不变) 2238889527584
深拷贝后---》b[0]引用的对象的值是: 小钟同学
**重点关注内嵌的列表对象**
深拷贝后---》b[1]引用地址 2238895013512
深拷贝后---》b[1]引用的对象的值是: ['内嵌列表的小钟同学']
==================深拷贝后==============>
==================深拷贝后====对比:b[0]和a[0]=============>
True
==================深拷贝后====对比:b[1]和a[1]=============>
False
纯属个人实践中相关经验之谈,如有纰漏,还希望大佬们多多提点!小钟同学 | 文 【原创】| QQ:308711822
原文作者:程序员小钟同学;如有侵权,请联系管理员删除。