Python基础——深浅拷贝、python内存泄露、你并不了解的format、decimal

深浅拷贝

先问问大家,什么是拷贝?拷贝是音译的词,其实他是从copy这个英文单词音译过来的,那什么是copy? copy其实就是复制一份,也就是所谓的抄一份。深浅copy其实就是完全复制一份,和部分复制一份的意思。

先看赋值运算

l1 = [1,2,3,['jason','egon']]
l2 = l1

l1[0] = 111
print(l1)  # [111, 2, 3, ['jason', 'egon']]
print(l2)  # [111, 2, 3, ['jason', 'egon']]

l1[3][0] = 'kevin'
print(l1)  # [111, 2, 3, ['kevin', 'egon']]
print(l2)  # [111, 2, 3, ['kevin', 'egon']]

对于赋值运算来说,l1与l2指向的是同一个内存地址,所以他们是完全一样的,在举个例子,比如张三李四合租在一起,那么对于客厅来说,他们是公用的,张三可以用,李四也可以用,但是突然有一天张三把客厅的的电视换成投影了,那么李四使用客厅时,想看电视没有了,而是投影了,对吧?l1,l2指向的是同一个列表,任何一个变量对列表进行改变,剩下那个变量在使用列表之后,这个列表就是发生改变之后的列表

浅拷贝copy

#同一代码块下:
l1 = [1, 'jason', True, (1,2,3), [22, 33]]
l2 = l1.copy()
print(id(l1), id(l2))  # 2713214468360 2713214524680
print(id(l1[-2]), id(l2[-2]))  # 2547618888008 2547618888008
print(id(l1[-1]),id(l2[-1]))  # 2547620322952 2547620322952

# 不同代码块下:
>>> l1 = [1, 'jason', True, (1, 2, 3), [22, 33]]
>>> l2 = l1.copy()
>>> print(id(l1), id(l2))
1477183162696
>>> print(id(l1[-2]), id(l2[-2]))
1477181814032
>>> print(id(l1[-1]), id(l2[-1]))
1477183162504

对于浅copy来说,只是在内存中重新创建了开辟了一个空间存放一个新列表,但是新列表中的元素与原列表中的元素是公用的

深拷贝deepcopy

# 同一代码块下
import copy
l1 = [1, 'jason', True, (1,2,3), [22, 33]]
l2 = copy.deepcopy(l1)
print(id(l1), id(l2))  # 2788324482440 2788324483016
print(id(l1[0]),id(l2[0]))  # 1470562768 1470562768
print(id(l1[-1]),id(l2[-1]))  # 2788324482632 2788324482696
print(id(l1[-2]),id(l2[-2]))  # 2788323047752 2788323047752

# 不同代码块下
>>> import copy
>>> l1 = [1, 'jason', True, (1, 2, 3), [22, 33]]
>>> l2 = copy.deepcopy(l1)
>>> print(id(l1), id(l2))
1477183162632
>>> print(id(0), id(0))
1470562736
>>> print(id(-2), id(-2))
1470562672
>>> print(id(l1[-1]), id(l2[-1]))
1477183162312


对于深copy来说,列表是在内存中重新创建的,列表中可变的数据类型是重新创建的,列表中的不可变的数据类型是公用的

相关面试题

l1 = [1, 2, 3, 4, ['jason']]
l2 = l1[::]
l1[-1].append(666)
print(l2)

python内存泄露

起因

内存泄露指由于疏忽或错误造成程序未能释放已经不再使用的内存的情况。内存泄漏并非指内存在物理上的消失,而是应用程序分配某段内存后,由于设计错误,失去了对该段内存的控制,因而造成了内存的浪费。导致程序运行速度减慢甚至系统崩溃等严重后果。有 del() 函数的对象间的循环引用是导致内存泄漏的主凶

方案

不使用一个对象时使用:delobject 来删除一个对象的引用计数就可以有效防止内存泄漏问题。通过Python 扩展模块 gc 来查看不能回收的对象的详细信息。可以通过 sys.getrefcount(obj) 来获取对象的引用计数,并根据返回值是否为 0 来判断是否内存泄漏。
但是由于gc垃圾收集机制,要遍历所有被垃圾收集器管理的python对象(包括垃圾和非垃圾对象),该过程比较耗时可能会造成程序卡顿,会对某些对内存、cpu要求较高的场景造成性能影响。那怎么才能优雅地避免内存泄露呢?

编写安全的代码

比如对于下面发生内存泄露的cycle_ref函数,在函数结束前解除循环引用,即可解决内存泄露问题。

def cycle_ref():
    a1 = A()
    a2 = A()
 
    a1.child = a2
    a2.child = a1
 
    # 解除循环引用,避免内存泄露
    a1.child  = None
    a2.child  = None

但是对于上述方法,我们有可能会忘记那一两行无关紧要的代码而造成灾难性后果,毕竟老虎也有打盹的时候。那怎么办?不要着急,Python已经为我们考虑到这点:弱引用。

弱引用

Python标准库提供了weakref模块,弱引用不会在引用计数中计数,其主要目的是解决循环引用。并非所有的对象都支持weakref,例如list和dict就不支持。下面是weakref比较常用的方法:

"""
1. class weakref.ref(object[, callback]) :创建一个弱引用对象,object是被引用的对象,callback是回调函数(当被引用对象被删除时,调用该回调函数)

2.weakref.proxy(object[, callback]):创建一个用弱引用实现的代理对象,参数同上

3.weakref.getweakrefcount(object) :获取对象object关联的弱引用对象数

4.weakref.getweakrefs(object):获取object关联的弱引用对象列表

5.class weakref.WeakKeyDictionary([dict]):创建key为弱引用对象的字典

6.class weakref.WeakValueDictionary([dict]):创建value为弱引用对象的字典

7.class weakref.WeakSet([elements]):创建成员为弱引用对象的集合对象
"""

同样对于上面发生内存泄露的cycle_ref函数,使用weakref稍加改造,便可更安全地解决内存泄露问题:

import weakref
 
class A(object):
    def __init__(self):
        self.data = [x for x in range(100000)]
        self.child = None
 
    def __del__(self):
        pass
 
def cycle_ref():
    a1 = A()
    a2 = A()
 
    a1.child = weakref.proxy(a2)
    a2.child = weakref.proxy(a1)
 
if __name__ == '__main__':
    import time
    while True:
        time.sleep(0.5)
        cycle_ref()

你并不了解的format、decimal

谈到format的用法,大家肯定会说不就是用来处理字符串拼接的嘛,谈到decimal你能说出这句话我只能说小伙子你还太嫩了呦~~~让我来装个X天秀一波

format格式化数字



decimal精确处理数字

posted @ 2022-02-26 22:12  喝茶看猴戏  阅读(113)  评论(0)    收藏  举报  来源