zoukankan      html  css  js  c++  java
  • 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程序由代码块组成的,我们所有的代码都需要依赖代码块执行,以下都是代码块。 (2)一个模块,一个函数,一个类,一个文件等都可以是一个代码块。 (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

  • 相关阅读:
    Cocos2d-x 使用物理引擎进行碰撞检测
    Cocos2d粒子系统二
    cocos2d(粒子效果编辑器)
    关于Cococs中的CCActionEase(下)
    关于Cococs中的CCActionEase(中)
    关于Cococs中的CCActionEase
    call()和apply() 的区别
    WebStorm 使用快捷键大全
    WebStorm 的使用(一)
    导入xlsx,文件到sqlite3数据库
  • 原文地址:https://www.cnblogs.com/youngchaolin/p/12523005.html
Copyright © 2011-2022 走看看