Python学习-内存地址、代码块、深浅copy、set

记录下python中is、id、==的用法场景、代码块、深浅copy和set集合的知识。

is id ==

is:判断的是内存地址是否相同。
id:获取内存地址。
==:比较的是值是否相等。

# id() 获取内存地址
print('------id() 获取内存地址------')
name = 'messi'
age = 33
print(id(name))
print(id(age))

# == 比较的是值是否相等
print('------== 比较的是值是否相等------')
l1 = [1, 2, 3]
l2 = [1, 2, 3]
print(l1 == l2) # True

s1='alex'
s2='alex'
print(s1==s2) # True

# is 判断的是内存地址是否相同
print('------is 判断的是内存地址是否相同------')
# l1和l2的内存地址不一样
print(id(l1))
print(id(l2))
print(l1 is l2) # False

# s1和s2的内存地址一样
print(id(s1))
print(id(s2))
print(s1 is s2) # True
# 可以看出s1和s2指向的都是同一个对象

控制台

从下面list和str的结果可以看出,id相同的值一定相同,值相同的,id不一定相同。

# 获取字符串和int数据的内存地址
------id() 获取内存地址------
4463605888
4461146288
# 列表和字符串的值一样,==比较就返回True
------== 比较的是值是否相等------
True
True
------is 判断的是内存地址是否相同------
# 列表的内存地址不一样,虽然值一样
4462878600
4462878664
False
# 字符串的内存地址一样,两个变量都指向内存中同一个地址,节省了内存空间
4463628216
4463628216
True

代码块

python程序由代码块组成的,我们所有的代码都需要依赖代码块执行,以下都是代码块。
(1)一个模块,一个函数,一个类,一个文件等都可以是一个代码块。
(2)交互方式下输入的每个命令,每行就是一个代码块,注意!是每行,就是一个代码块。
上面的例子可以看出,s1和s2都指向了同一个内存地址,这用到了缓存机制。在同一个代码块下,有一个缓存机制,在其他的代码块下,遵循其他代码块的机制,类似大陆和香港一国两制。

同一代码块缓存机制

python在执行同一个代码块,初始化对象的命令时,首先会去检查这个值是否存在,如果存在就会重用。本质上是初始化对象时,将变量名和变量值存在一个字典里,如果下次初始化一个新的变量,会首先在字典里查询是否有对应的值,如果有就将新的变量指向这个值,达到值的重用,虽然变量名不一样,但是其id值是一样的。
适用的对象
int、bool、str,即所有的数字(bool底层也是数字)、还有几乎所有的字符串。
优点
节省内存空间,提升性能,可以联想一个宿舍买4个拖把和1个拖把的区别,显然买1个拖把大家共用它会更好的利用资源。

上面代码块的类型,可以看出同一个文件中的代码就处于一个代码块,测试下缓存机制。

# 同一代码块下的缓存机制
s1='alex' # 初始化命令
s2='alex' # 初始化命令
# id一样,实现重用
print(id(s1))
print(id(s2))
# 指向的对象相同,使用了缓存机制
print(s1 is s2) # True

# 适用对象测试
# 1 int
print('------int------')
i1=100
i2=100
print(i1 is i2)
# 2 bool
print('------bool------')
b1=True
b2=True
print(b1 is b2)
# 3 str
print('------str------')
s1='messi'
s2='messi'
print(s1 is s2)
# 4 list
print('------list------')
l1=[1,2,3]
l2=[1,2,3]
print(l1 is l2)
# 5 dict
print('------dict------')
d1={'name':'messi','age':34}
d2={'name':'messi','age':34}
print(d1 is d2)
# 6 tuple
print('------tuple------')
t1=(1,2,3)
t2=(1,2,3)
print(t1 is t2)

控制台可以看出,int、bool、str都可以实现缓存机制,虽然tuple也返回true,那是因为它的元素都是int,如果元素里面有列表,就返回false,缓存机制不生效。

# 同一代码块下的缓存机制,id一样,实现重用
4494130008
4494130008
True
# 同一代码块缓存机制适用对象测试
------int------
True
------bool------
True
------str------
True
------list------
False
------dict------
False
# 如果tuple里的元素添加一个列表进去,缓存机制就不生效
------tuple------
True

不同代码块缓存机制

python会有一个小数据池,里面会为int分配一定的数据,str也分配一定的数据,如果初始化对象有这个数据,就直接使用。类似同一代码块下的机制,只是它所缓存的数据量比较少。
适用的对象
int、bool、str,这里的数据范围有限。
int分配的数据范围是-5~256,bool本质上就是0或1,也在范围内。
str分配的是一定规则的少量字符串,具体参考文末博文。
优点
节省内存空间,提升性能。

上面代码块的类型,可以看出命令行的每一行都处于不同的代码块,利用它测试下不同代码块下的缓存机制。

# 大小写字母,数字,下划线的组合,是会驻留的
>>> s1='alex'
>>> s2='alex'
>>> print(s1 is s2)
True
# 100在-5到256之间,驻留
>>> i1=100
>>> i2=100
>>> print(i1 is i2)
True
# 300超出范围,不驻留
>>> i3=300
>>> i4=300
>>> print(i3 is i4)
False
# 不论创建多个True或False,小数据池都只有一份      
>>> b1=True
>>> b2=True
>>> print(b1 is b2)
True 

深浅copy

浅copy:嵌套的可变数据类型如list,dict,copy后指向的是同一个内存空间。
深copy:嵌套的可变数据类型如list,dict,copy后指向的是不同的内存空间。

看几个例子,可以更好的理解浅copy,判断是不是浅copy需要看可变数据类型是不是指向同一个内存空间。

(1)数据类型不可变

# 浅copy
l1=[1,2,3]
l2=l1.copy()
l1.append(4)
# 查看元素的id,都一样
print(id(l1[0]))
print(id(l2[0]))
# 这里产生了两个id
print(l1,id(l1)) # [1, 2, 3, 4]
print(l2,id(l2)) # [1, 2, 3]

控制台

# 查看元素的id,都一样
4378635440
4378635440
# l1和l2整体的id不一样
[1, 2, 3, 4] 4381086984
[1, 2, 3] 4381086344

l1和l2的id不一样,但是里面元素的id一样,说明l1和l2里的元素,都指向内存中同一份内存空间。

(2)数据类型可变

# 浅copy
l1=[1,2,3,['messi','ronald']]
l2=l1.copy()
l1[-1].append('herry')
# 列表里的每个元素,id一样
print(id(l1[-1]))
print(id(l2[-1]))
# 但是整体l1和l2的id不一样
print(l1,id(l1)) # [1, 2, 3, ['messi', 'ronald', 'herry']]
print(l2,id(l2)) # [1, 2, 3, ['messi', 'ronald', 'herry']]

控制台

# l1和l2里元素,指向的内存空间一样
4423151496
4423151496
# 其中一个修改列表,另一个指向同一个空间,相应的也修改了
[1, 2, 3, ['messi', 'ronald', 'herry']] 4423874568
[1, 2, 3, ['messi', 'ronald', 'herry']] 4423873800

同样,l1和l2里的元素,指向的内存空间一样,由于l1修改了可变列表,l2指向同一个可变列表,因此l2打印结果也修改了。

在上面的基础上,如果不修改列表里的数据,修改的是int类型的数据呢,下面会是什么结果?

# 浅copy
l1=[1,2,3,['messi','ronald']]
l2=l1.copy()
# 修改int类型数据
l1[0]=520
# 重新查看元素的id
print(id(l1[0]),id(l1[1]),id(l1[2]),id(l1[3]))
print(id(l2[0]),id(l2[1]),id(l2[2]),id(l2[3]))
print(l1,id(l1))
print(l2,id(l2))

分析一下,int类型不可变,l1[0]会在内存中新开辟一个空间储存520这个数字。

控制台查看,说明分析是ok的。

# l1[0]和l2[0]指向的空间不一样,其他元素一样
4423020496 4421405904 4421405936 4423873160
4421405872 4421405904 4421405936 4423873160
# 打印结果印证了以上分析
[520, 2, 3, ['messi', 'ronald']] 4424128456
[1, 2, 3, ['messi', 'ronald']] 4423874568

看下面例子,可以更好的理解深copy,判断是不是深copy需要看可变数据类型是不是指向不同内存空间。

# 深copy
import copy
l1=[1,2,3,['messi','ronald']]
l2=copy.deepcopy(l1)
# 给l1元素赋值
l1[-1].append('herry')
# 重新查看元素的id
print(id(l1[0]),id(l1[1]),id(l1[2]),id(l1[3]))
print(id(l2[0]),id(l2[1]),id(l2[2]),id(l2[3]))
print(l1)
print(l2)

控制台

# 只有最后一个可变数据类型,指向的空间不一样
4421405872 4421405904 4421405936 4424130056
4421405872 4421405904 4421405936 4424364872
# l1修改了不影响l2
[1, 2, 3, ['messi', 'ronald', 'herry']]
[1, 2, 3, ['messi', 'ronald']]

可以看出,不可变数据类型是共用内存空间,而可变数据类型如列表list就是开辟一个新的内存空间。

以上例子可以总结出如下的示意图,可便于理解。列表在内存中copy完后,在不同的空间,因此id肯定不一样,但是它里面的元素,保存的是内存地址(以111等数字示意)指向内存中的实际内容,下图可以看出深浅copy的不同。

另外,切片是属于浅拷贝。

# 面试题 切片是深拷贝还是浅拷贝?
l1=[1,2,3,['messi','ronald']]
l2=l1[:] # 全切片
l1[-1].append('herry')
print(id(l1[0]),id(l1[1]),id(l1[2]),id(l1[3]))
print(id(l2[0]),id(l2[1]),id(l2[2]),id(l2[3]))
# 打印结果可以看出是浅拷贝
print(l1)
print(l2)

set

集合set非常重要,这里这里暂时入门一下,它属于python的基础数据类型之一,属于容器型数据类型,要求元素是不可变的数据类型,如int、bool和str。但是它本身是可变的数据类型,另外集合是无序的。Set在java中也可以去重,并提供了HashSet和TreeSet等子类,它里面的元素可以是自定义对象,其中TreeSet还能自动排序。Set在scala中叫做散列,也可以去重,交集、并集、差集等方法比较相似。

集合的作用:
(1)列表的去重,去重不保证顺序。
(2)关系测试:交集、并集、差集、反交集等。

可以直接创建,也可以使用set()创建,元素必须不可变。如果想创建一个空集合,使用set()方法创建,注意区分空字典。

# 直接创建
s1 = {1, 2, 3, True, 'messi'}
print(s1, type(s1))
# 使用Set创建
s2 = set({1, 2, 3, True, 'messi'})
print(s2, type(s2))

# 空集合
s3 = set()
print(s3, type(s3))  # set() <class 'set'>
# 这是字典
s4 = {}
print(s4, type(s4))  # 这不是空集合,是空字典

控制台

# 创建字典
{1, 2, 3, 'messi'} <class 'set'>
{1, 2, 3, 'messi'} <class 'set'>
# 空集合和空字典
set() <class 'set'>
{} <class 'dict'>

可以使用add增,也可以使用update。

# 增
set1 = {'messi', 'ronald'}
set1.add('herry')
print(set1)

# update迭代增加
set1.update('kk')
print(set1)  

控制台

# 增加了herry
{'ronald', 'messi', 'herry'}
# 本来两个k,去重处理了
{'k', 'ronald', 'messi', 'herry'}

可以使用remove按元素删除,也可以pop随机删除。

set1 = {'k', 'ronald', 'messi', 'herry'}
# 删
set1.remove('k')
print(set1)

# 随机删除
# set1.pop()
# print(set1)

控制台

{'ronald', 'messi', 'herry'}

set的改,就是删除+增加的结合体。

set1={'ronald', 'messi', 'herry'}
# 改
set1.remove('herry')
set1.add('clyang')
print(set1) # 修改成{'ronald', 'messi', 'clyang'}

关系测试

# 交集 并集 差集 反交集
set1 = {1, 2, 3, 4, 5, 6}
set2 = {4, 5, 6, 7, 8, 9}
# 交集 scala中是set散列也有类似操作,是&或intersect
print(set1 & set2)
print(set1.intersection(set2))

# 并集 scala中是++或者union
print(set1 | set2)
print(set1.union(set2))

# 差集 scala中是&~或者diff
print(set1 - set2)
print(set1.difference(set2))

# 反交集
print(set1 ^ set2)
print(set1.symmetric_difference(set2))

# 子集
set1={1,2,3}
set2={1,2,3,4,5,6}
# set1是不是set2的子集
print(set1 < set2)
print(set1.issubset(set2))

# set2是不是set1的超集
print(set2 > set1)
print(set2.issuperset(set1))

控制台

# 交集
{4, 5, 6}
{4, 5, 6}
# 并集
{1, 2, 3, 4, 5, 6, 7, 8, 9}
{1, 2, 3, 4, 5, 6, 7, 8, 9}
# 差集,set1-set2,我有你没有
{1, 2, 3}
{1, 2, 3}
# 反交集,就是交集之外的部分
{1, 2, 3, 7, 8, 9}
{1, 2, 3, 7, 8, 9}
# set1是不是set2的子集
True
True
# set2是不是set1的超集
True
True

列表去重

使用set对列表去重,不保证顺序。

# 列表先转换为set,然后set再转换为列表,即完成去重
l=[1,2,2,2,2,3,4,5]
s=set(l)
l=list(s)
print(l) # [1, 2, 3, 4, 5]

以上,留作后面查看用。

参考博文:

(1)https://www.cnblogs.com/jin-xin/articles/9439483.html

(2)https://www.cnblogs.com/youngchaolin/p/12375032.html

posted @ 2020-03-19 11:37  斐波那切  阅读(223)  评论(0编辑  收藏  举报