Python的垃圾回收机制、代码规范及命令行参数


文章目录

  • 1、Python的垃圾回收机制
  • 2、Python的引用计数机制
  • 3、Python中的循环数据结构及引用计数
  • 4、Python中的GC模块
  • 5、Python的内存优化
  • 6、Python的pep8规范
  • 7、Python的命令行参数
  • 总结

1、Python的垃圾回收机制

  垃圾回收机制的必要性:程序长时间占用的内存需要释放。
  内存泄漏:由于开发人员的疏忽或则错误引起的程序未能释放已经不再使用的内存。内存泄漏并非指内存在物理上的消失,而是意味着代码在分配了某段内存后,因为设计错误,失去了对这段内存的控制,从而造成了内存的浪费。常见的有循环引用导致的内存泄漏问题。

# 内存泄漏测试
import gcclass ClassA(object):def __init__(self):print("对象产生,id:%s" % str(id(self)))def f2():while True:c1 = ClassA()c2 = ClassA()c1.t = c2   # 动态添加t属性(实例属性)c2.t = c1   # 动态添加t属性(实例属性)del c1del c2# gc.collect()gc.disable()  # Python默认是开启垃圾回收机制的,可以通过下面的代码关闭
f2()
# 执行f2(),进程占用的内存会不断增大。
# 创建了c1、c2后这两块内存的引用计数都是1,执行c1.t = c2、c2.t = c1后,这两块内存的引用计数都是2
# 在del c1后,引用计数为1,由于不是0,所以c1对象不会被销毁。
# Python默认是开启垃圾回收机制的,关闭后,导致内存泄漏

2、Python的引用计数机制

  概述:引用计数机制(Garbage Collection),Python内部使用引用计数,来保持追踪内存中的对象,Python内部记录了对象有多少个引用,即引用计数,当对象被创建时就创建了一个引用计数,当对象不再需要时,这个对象的引用计数为0时,它被垃圾回收。
  引用计数是一种垃圾收集机制,而且也是一种最直观、最简单的垃圾收集技术。Python的垃圾回收采用的是引用计数机制为主和分代回收机制、标记-清除机制为辅的结合机制,当对象的引用计数变为0时,对象将被销毁,除了解释器默认创建的对象外(默认对象的引用计数永远不会变成0)。
  引用计数机制的任务:
 1.为新生成的对象分配内存;
 2.识别哪些是垃圾对象;
 3.从垃圾对象中回收内存。
  Python中的每一个东西都是对象,它们的核心就是一个结构体:PyObject

typedef struct object {int ob_refcnt;struct_typeobject * ob_refcnt
}PyObject;

  PyObject是每个对象必有的内容,其中ob_refcnt就是做为引用计数的。当一个对象有新的引用时。它的ob_refcnt就会增加,当引用它的对象被删除,它的ob_refcnt就会减少,当引用计数为0时,该对象就没有了。

# 查看一个变量的引用次数:sys.getrefcount( )
import sysa = []
print(sys.getrefcount(a))  # 2次b = a
print(sys.getrefcount(a))  # 3次c = b
d = b
e = c
f = e
g = d
print(sys.getrefcount(a))  # 8次

  在python中可以通过sys.getrefcount()函数查看一个变量的引用次数。需注意:
 1.getrefcount()本身也会引入一次计数;
 2.在函数调用发生的时候,会产生额外的两次引用,一次来自函数栈,另一个是函数参数。

import sysdef func(a):print(sys.getrefcount(a))a = []
func(a)
b = a
c = b
print(sys.getrefcount(a))

3、Python中的循环数据结构及引用计数

  引用计数的缺点:
 1.维护引用计数会消耗资源;
 2.循环数据结构及引用计数。

import os
import sys
import psutil  # 获取系统运行的进程和系统利用率def ShowMemorySize(tag):pid = os.getpid()        # 获取进程idp = psutil.Process(pid)  # 获取进程对象info = p.memory_full_info()  # 占用的内存信息memory = info.uss / 1024. / 1024print('{} memory used: {} MB'.format(tag, memory))# 验证循环引用的情况
def func():ShowMemorySize('初始化')a = [i for i in range(10000000)]b = [i for i in range(10000000)]a.append(b)  # 相互的引用b.append(a)  # 相互的引用ShowMemorySize('创建列表对象a、b之后')
#     print(sys.getrefcount(a))
#     print(sys.getrefcount(b))del a  # 即使显式删除,但是内存没有释放del bfunc()
ShowMemorySize('完成之后的情况')  # 程序执行完成后依然占据内存
# 程序执行完,但是内存没有释放,
# 如果这种错误出现在生产环境中,a和b一开始占用的空间不是很大,但经过长时间运行后
# Python 所占用的内存一定会变得越来越大,后果不堪设想。

  此时程序中的例子成了一个"孤岛",一组未使用的、互相指向的对象,都没有外部指向。换句话说,因为所有的引用计数都是1而不是0,Python的引用计数机制不能够处理相互指向自己的对象。

import os
import gc
import sys
import psutil  # 获取系统运行的进程和系统利用率def ShowMemorySize(tag):pid = os.getpid()        # 获取进程idp = psutil.Process(pid)  # 获取进程对象info = p.memory_full_info()  # 占用的内存信息memory = info.uss / 1024. / 1024print('{} memory used: {} MB'.format(tag, memory))# 验证循环引用的情况
def func():ShowMemorySize('初始化')a = [i for i in range(10000000)]b = [i for i in range(10000000)]a.append(b)  # 相互的引用b.append(a)  # 相互的引用ShowMemorySize('创建列表对象a、b之后')func()
gc.collect()  # 可以通过显示手动gc.collect()释放资源
ShowMemorySize('完成之后的情况')

  信息-标记清除:一种基于追踪回收技术实现的垃圾回收算法(Tracing GC)。它分为两个阶段:
 1.第一阶段是标记阶段,GC会把所有的活动对象打上标记(垃圾检测);
 2.第二阶段是把那些没有标记的非活动对象进行回收(垃圾回收)。
  首先初始化所有对象并标记为白色,确定根节点对象(根节点对象不会被删除),将它们标记为黑色(表示对象有效)。然后将有效对象引用的对象标记为灰色(表示对象可达,但是它们所引用的对象还没检查),检查完灰色对象引用的对象后,将灰色对象标记为黑色,重复直到不存在灰色节点为止。最后白色节点都是需要清除的对象。

  分代回收:一种以空间换时间的操作方式(Generationa GC)。Python将所有对象分为三代,分别为年轻代(第0代,Generationa Zero)、中年代(第1代)、老年代(第2代)。它们对应的是3个链表,它们的垃圾收集频率与对象的存活时间的增大而减小。新创建的对象都会分配在年轻代,年轻代链表的总数达到上限时,Python垃圾收集机制就会被触发,把那些可以被回收的对象回收掉,而那些不会回收的对象就会被移到中年代去,依此类推,老年代中的对象是存活时间最久的对象,甚至是存活于整个系统的生命周期内。同时,分代回收是建立在标记清除技术基础之上。分代回收同样作为Python的辅助垃圾收集技术处理那些容器对象。

import objgraph  # 查看内存调用关系a = [1, 2, 3, 4]
b = [5, 6, 7, 8]
c = [1, 2, 3, a, b]a.append(b)
b.append(a)
a.append(c)objgraph.show_backrefs([a])


4、Python中的GC模块

  有三种情况会触发垃圾回收机制:
 1.当gc模块的计数器达到阈值的时候,自动回收垃圾;
 2.调用gc.collect(),手动回收垃圾;
 3.程序推出的时候,Python解释器来回收垃圾。
  gc模块中有一个长度为3的列表计算器,可以通过gc.get_count()获取。

import gcclass A(object):passprint(gc.get_count())a = A()
print(gc.get_count())del a
print(gc.get_count())

  (244, 10, 1):244是指距离上一次一代检查,Python分配内存的数目减去释放内存的数目,注意是内存分配,而不是引用计数器的增加。10是指距离上一次二代检查,一代垃圾检查的次数。10是指距离上一次三代检查,二代垃圾检查的次数。
  gc模块中有一个自动垃圾回收的阈值,可以通过gc.get_threshold()获取到长度为3的元组。每一次计数器的增加,gc模块就会检查增加后的计数器是否达到阈值数目,到达阈值数目就会执行对应的代数的垃圾检查,然后重置计数器。

import gc
print(gc.get_threshold())

  假设阀值是(700,10,10):
  当计数器从(699,3,0)增加到(700,3,0),gc模块就会执行gc.collect(0),即检查一代对象的垃圾,并重置计数器为(0,4,0);
  当计数器从(699,9,0)增加到(700,9,0),gc模块就会执行gc.collect(1),即检查一、二代对象的垃圾,并重置计数器为(0,0,1);
  当计数器从(699,9,9)增加到(700,9,9),gc模块就会执行gc.collect(2),即检查一、二、三代对象的垃圾,并重置计数器为(0,0,0)。


5、Python的内存优化

  小整数与大整数对象池:
  Python为了优化速度,使用了小整数池对象,避免为整数频繁申请和销毁内存空间。
  Python对小整数的定义是[-5,256],这些整数对象是提前建立好的,不会被垃圾回收。
  每一个大整数都会创建一个新的对象。

# 小整数
a = 1
print("a:", id(a))b = 1
print("b:", id(b))del a
del bc = 1
print("c:", id(c))
# 大整数
a = 10000
print("a:", id(a))b = 10000
print("b:", id(b))

6、Python的pep8规范

  PEP8规范,简单说就是一种编码规范,是为了让代码“更好看”,更容易被阅读。 较为重要的注意事项总结如下:

1.缩进:每级缩进使用四个空格(绝对不要混用Tab和空格)。2.换行:限制每行的最大长度为79个字符;使用 "\"或"()"控制换行。3.空行:顶层函数和类之间使用两个空行;类的方法之间用一个空行;在函数中使用空一行表示不同逻辑段落。4.导入:import位于文件的顶部;不要使用 from foo imort *(不要使用隐式的相对导入)。5.空格:在list,dict,tuple,set中的元素后的","后面加一个空格;dict的":"后面加一个空格;注释符号#后面加一个空格;操作符两端加一个空格;在参数列表里的"="两端不需要空格。6.注释:注释要保持与时俱进,一句后面两个空格跟注释符号#。7.命名规范:使用有意义的,英文单词或词组,绝对不要使用汉语拼音;不要使用大小写的L、大写的O作为变量名;类名首字母大写,内部类加上前导下划线;函数名应该小写,增强可读性可以使用下划线分割。8.其他:别用"=="进行布尔值和True或者False的比较应该用is。

  Python之禅:作为一个Python开发者,如果不知道什么是Python之禅,那是不可饶恕的。

# 打开Python IDE,输入import this会出现一段文字,这就是《The Zen Of Python》:
import this

Python之禅,by Tim Peters优美胜于丑陋(Python 以编写优美的代码为目标)
明了胜于晦涩(优美的代码应当是明了的,命名规范,风格相似)
简洁胜于复杂(优美的代码应当是简洁的,不要有复杂的内部实现)
复杂胜于凌乱(如果复杂不可避免,那代码间也不能有难懂的关系,要保持接口简洁)
扁平胜于嵌套(优美的代码应当是扁平的,不能有太多的嵌套)
间隔胜于紧凑(优美的代码有适当的间隔,不要奢望一行代码解决问题)
可读性很重要(优美的代码是可读的)
即便假借特例的实用性之名,也不可违背这些规则(这些规则至高无上)
不要包容所有错误,除非你确定需要这样做(精准地捕获异常,不写except:pass风格的代码)
当存在多种可能,不要尝试去猜测
而是尽量找一种,最好是唯一一种明显的解决方案(如果不确定,就用穷举法)
虽然这并不容易,因为你不是 Python 之父(这里的Dutch是指 Guido)
做也许好过不做,但不假思索就动手还不如不做(动手之前要细思量)
如果你无法向人描述你的方案,那肯定不是一个好方案;反之亦然(方案测评标准)
命名空间是一种绝妙的理念,我们应当多加利用(倡导与号召)

7、Python的命令行参数

  Python可以通过sys模块中的sys.argv来获取命令行参数。

# 创建一个test_1.py的文件
# test_1.py
import sysprint("参数个数为:", len(sys.argv),"个参数")
print("参数列表:", str(sys.argv))

  在命令行中执行:

python test_1.py
python test_1.py 1 2 3 4 5 6 7


  argv返回命名行参数是一个列表,第一个元素是.py文件的文件名。sys.argv支持Python的切片操作,可以获取参数,不需要文件名。sys.argv只提供了比较简单的命令参数获取方式,并没有提供命令提示。

# 创建一个test_1.py的文件
# test_1.py
import sysprint("参数个数为:", len(sys.argv),"个参数")
print("参数列表:", str(sys.argv[1:]))

  在命令行中执行:

python test_1.py 1 2 3 4 5 6 7


  argparse模块:可以轻松编写用户友好的命令行界面。模块定义了它需要的参数,可以顺利解析参数。可以自动生成帮助信息和用法信息。在用户给出无效参数时发出错误。
 基本结构:
 1.引入包;
 2.创建参数对象;
 3.添加参数;
 4.解析对象。

# 1 基本结构
import argparse                    # 引入argparse包parser = argparse.ArgumentParser()  # 创建参数对象
parser.add_argument()              # 添加参数
args = parser.parse_args()         # 类似于类的实例化,解析对象
# 2 创建对象参数
# 类似于创建一个类parser = argparse.ArgumentParser()。
# 需要注意:可以把添加描述信息parser.description = '……' 放到创建对象参数里面,描述信息就是–help时的提示信息
import argparseparser = argparse.ArgumentParser(description='parser demo')# parser = argparse.ArgumentParser()
# parser.description = 'parser demo'  # 分开写parser.add_argument('A', help='argument for A')
args = parser.parse_args()

# 3 添加参数
# name or flags(位置参数):必须要写的参数。
import argparseparser = argparse.ArgumentParser(description='parser demo')
parser.add_argument('a', help='argument for a', type=int)
parser.add_argument('b', help='argument for b', type=int)
args = parser.parse_args()
multi = args.a * args.b
print(multi)

# 可选参数参数:以"-"开头的参数是可选的参数。
import argparsedef main():parser = argparse.ArgumentParser(description="Demo of argparse")parser.add_argument('-n', '--name', default='Lucy')  # "-n","-name"表示同一个参数parser.add_argument('-s', '--sex', default='male')  # default表示在默认参数args = parser.parse_args()print(args)name = args.namesex = args.sexprint('Hello {}  {}'.format(name, sex))if __name__ == '__main__':main()

# choices:参数值只能从几个选项里面选择
import argparsedef get_parser():parser = argparse.ArgumentParser(description='choices demo')# 输入的值只能从alexnet和vgg中选择parser.add_argument('-arch', required=True, choices=['alexnet', 'vgg'])# required: 表示这个参数是否一定需要设置return parserif __name__ == '__main__':parser = get_parser()args = parser.parse_args()print('the arch of CNN is {}'.format(args.arch))

# action:在命令行遇到此参数时要采取的基本操作类型。
import argparseparser = argparse.ArgumentParser()
parser.add_argument('--foo', action='append')
args = parser.parse_args()
print(args.foo)

# 4 解析参数
# ArgumentParser通过该parse_args()方法解析参数。
# 这将检查命令行,将每个参数转换为适当的类型,然后调用相应的操作。
import argparse# 创建一个解析器对象
parse = argparse.ArgumentParser(prog="登陆系统", description="系统自定义命令行文件")# 添加位置参数(必选参数)
parse.add_argument("LoginType", type=str, help="登陆系统类型")# 添加可选参数
parse.add_argument("-u", dest="user", type=str, help="用户名")
parse.add_argument("-p", dest="pwd", type=str, help="密码")# 解析参数
result = parse.parse_args()# 进行简单逻辑判断
if (result.user == "root" and result.pwd == "111111"):print("Login Success!")
else:print("Login Fail!")


总结

  GC作为现代编程语言的自动内存管理机制,专注于两件事:1.找到内存中无用的垃圾资源; 2.清除这些垃圾并把内存让出来给其它对象使用。
  GC彻底把程序员从资源管理的重担中解放出来,投入更多的精力和时间在业务逻辑上。但这并不意味着就可以不去了解GC,毕竟多了解GC的知识有利于写出更健壮的代码。

【Python知识点梳理】10.Python的垃圾回收机制、代码规范及命令行参数相关推荐

  1. php垃圾回收算法分代,PHP的垃圾回收机制代码实例讲解

    PHP可以自动进行内存管理,清除不需要的对象,主要使用了引用计数 在zval结构体中定义了ref_count和is_ref , ref_count是引用计数 ,标识此zval被多少个变量引用 , 为0 ...

  2. python实现链表的删除_Python垃圾回收机制

    python作为一门解释型语言,以代码简洁易懂著称.我们可以直接对名称赋值,而不必声明类型.名称类型的确定.内存空间的分配与释放都是由python解释器在运行时进行的.python这一自动管理内存功能 ...

  3. 你应该了解的python 垃圾回收机制

    引用计数器回收 一个对象,会记录着自身被引用的个数 每增加一个引用,这个对象的引用计数会自动+1 每减少一个引用,这个对象的引用计数会自动-1 查看引用计数 import sys sys.getref ...

  4. python垃圾回收机制原理_如何理解和掌握Python垃圾回收机制?

    在编程世界里,当一个对象失去引用或者离开作用域后,它就会被当做垃圾而被自动清除,这就是垃圾回收机制.在现在的高级语言如Python.Java都使用了垃圾回收机制,不过与Java采用的垃圾收集机制不同, ...

  5. 简述Python垃圾回收机制

    引言 许多高级语言都具有自己的垃圾回收机制,以管理计算机内存,Python也不例外.对于垃圾回收机制的了解程度,成了开发人员是否真正了解Python的检验手段,在面试的时候许多面试官也喜欢以此作为题目 ...

  6. Python3.5源码分析-垃圾回收机制

    Python3源码分析 本文环境python3.5.2. 参考书籍<<Python源码剖析>> python官网 Python3的垃圾回收概述 随着软硬件的发展,大多数语言都已 ...

  7. 面试题:垃圾回收机制(GC)

    一.垃圾回收对象 文章目录 一.垃圾回收对象 二.垃圾回收的基本流程 2.1 判断对象是否可以被回收(是否为垃圾) 方法一.引用计数算法 方法二.可达性分析算法 2.2 垃圾回收算法 方法一.标记清除 ...

  8. 4.16Day21垃圾回收机制、re模块正则

    ```python ```今日内容: 1.垃圾回收机制 2.re模块:正则 一.垃圾回收机制 不能被程序访问的数据,就是垃圾 1.引用计数 引用计数是用来记录值的内存地址被记录次数的 每一次对值地址的 ...

  9. Python必备基本技能——命令行参数args详解

    Python必备基本技能--命令行参数args详解 1. 效果图 2. 源码 2.1 简单命令行参数 2.1 轮廓检测源代码 参考 这篇博客将介绍一项开发人员.工程师和计算机科学家必备的技能--命令行 ...

最新文章

  1. python json解析列表显示_求教一下 python 读取 json 以后,输出的问题(unicode 和中文显示)...
  2. 服务器系统摁c,如何系统有效学习c服务器开发
  3. 用select 语句中的START WITH...CONNECT BY PRIOR子句实现递归查询
  4. NOSQL的Redis的基础
  5. 算盘与电子计算机的区别之一是,算盘与电子计算机的区别之一是算盘没有存储能力...
  6. mysql语句的执行顺序_SQL语句完整的执行顺序(02)
  7. Java Bean Validation 最佳实践
  8. web安全测试---WebScarab工具介绍(中间攻击,可以修改请求参数)
  9. linux安装系统ftp服务器配置,linux系统搭建ftp服务器的配置方 - 电子发烧友网
  10. 学习minix 3(未完成)
  11. sqlalchemy in查询优化_SQL高级:代码实战 SQL 单表优化
  12. CSS3制作3D水晶糖果按钮
  13. 集群式游戏服务器架构方案设计开发
  14. ORA-12638处理
  15. mysql索引小结_[数据库]mysql索引小结_星空网
  16. P-SIF长文本表示方法
  17. 各类dp的总结+例题
  18. MySQL 权限操作
  19. QQ互联开发者认证一直审核未通过的原因
  20. 字符串分割(split),将字符串按照指定字符进行分割。split(String regex)和split(String regex, int limit)

热门文章

  1. 数学之美|斐波那契数列与黄金分割
  2. SQL Server2005安装教程
  3. donkey car环境搭建
  4. 阿里巴巴校园招聘——灵犀互娱、游戏研发工程师、一面面经
  5. 字符型变量与整型变量的比较
  6. Java编程心得体会
  7. ajax的开发工具,Aptana(AJAX开发工具)
  8. windows HBA卡 查询WWN号
  9. BurpSuite安装插件教程:BurpCrypto: 万能网站密码爆破测试工具
  10. 「TCG 规范解读」简介-MISC-TPM 工作组