4- 补充话题
垃圾回收
1. 小整数对象池
整数在程序中的使用非常广泛,Python为了优化速度,使用了小整数对象池, 避免为整数频繁申请和销毁内存空间。
Python 对小整数的定义是 [-5, 256] 这些整数对象是提前建立好的,不会被垃圾回收。在一个 Python 的程序中,所有位于这个范围内的整数使用的都是同一个对象.
同理,单个字母也是这样的。
2. 大整数对象池
每一个大整数,均创建一个新的对象。
3. intern机制
a1 = "HelloWorld" a2 = "HelloWorld" a3 = "HelloWorld" a4 = "HelloWorld" a5 = "HelloWorld" a6 = "HelloWorld" a7 = "HelloWorld" a8 = "HelloWorld" a9 = "HelloWorld"
python会不会创建9个对象呢?在内存中会不会开辟9个”HelloWorld”的内存空间呢? 想一下,如果是这样的话,我们写10000个对象,比如a1=”HelloWorld”…..a1000=”HelloWorld”, 那他岂不是开辟了1000个”HelloWorld”所占的内存空间了呢?如果真这样,内存不就爆了吗?所以python中有这样一个机制——intern机制
,让他只占用一个”HelloWorld”所占的内存空间。靠引用计数去维护何时释放。
总结
- 小整数[-5,256]共用对象,常驻内存
- 单个字符共用对象,常驻内存
- 单个单词,不可修改,默认开启intern机制,共用对象,引用计数为0,则销毁
字符串(含有空格),不可修改,没开启intern机制,不共用对象,引用计数为0,销毁
大整数不共用内存,引用计数为0,销毁
数值类型和字符串类型在 Python 中都是不可变的,这意味着你无法修改这个对象的值,每次对变量的修改,实际上是创建一个新的对象
垃圾回收(二)
1. Garbage collection(GC垃圾回收)
现在的高级语言如java,c#等,都采用了垃圾收集机制,而不再是c,c++里用户自己管理维护内存的方式。自己管理内存极其自由,可以任意申请内存,但如同一把双刃剑,为大量内存泄露,悬空指针等bug埋下隐患。 对于一个字符串、列表、类甚至数值都是对象,且定位简单易用的语言,自然不会让用户去处理如何分配回收内存的问题。 python里也同java一样采用了垃圾收集机制,不过不一样的是: python采用的是引用计数机制为主,分代收集机制为辅的策略
引用计数机制:
python里每一个东西都是对象,它们的核心就是一个结构体:PyObject
typedef struct_object { int ob_refcnt; struct_typeobject *ob_type; } PyObject;
PyObject是每个对象必有的内容,其中ob_refcnt就是做为引用计数。当一个对象有新的引用时,它的ob_refcnt就会增加,当引用它的对象被删除,它的ob_refcnt就会减少
#define Py_INCREF(op) ((op)->ob_refcnt++) //增加计数 #define Py_DECREF(op) \ //减少计数 if (--(op)->ob_refcnt != 0) \ ; \ else \ __Py_Dealloc((PyObject *)(op))
当引用计数为0时,该对象生命就结束了。
引用计数机制的优点:
- 简单
- 实时性:一旦没有引用,内存就直接释放了。不用像其他机制等到特定时机。实时性还带来一个好处:处理回收内存的时间分摊到了平时。
引用计数机制的缺点:
- 维护引用计数消耗资源
- 循环引用
list1 = [] list2 = [] list1.append(list2) list2.append(list1)
list1与list2相互引用,如果不存在其他对象对它们的引用,list1与list2的引用计数也仍然为1,所占用的内存永远无法被回收,这将是致命的。 对于如今的强大硬件,缺点1尚可接受,但是循环引用导致内存泄露,注定python还将引入新的回收机制。(分代收集)
2. 画说 Ruby 与 Python 垃圾回收
英文原文: visualizing garbage collection in ruby and python
2.1 应用程序那颗跃动的心
GC系统所承担的工作远比"垃圾回收"多得多。实际上,它们负责三个重要任务。它们
为新生成的对象分配内存
识别那些垃圾对象,并且
从垃圾对象那回收内存。
2.2 一个简单的例子
下面是一个简单类,分别用Python和Ruby写成,我们今天就以此为例:
顺便提一句,两种语言的代码竟能如此相像:Ruby 和 Python 在表达同一事物上真的只是略有不同。但是在这两种语言的内部实现上是否也如此相似呢?
2.3 Ruby 的对象分配
当我们执行上面的Node.new(1)时,Ruby到底做了什么?Ruby是如何为我们创建新的对象的呢? 出乎意料的是它做的非常少。实际上,早在代码开始执行前,Ruby就提前创建了成百上千个对象,并把它们串在链表上,名曰:可用列表。下图所示为可用列表的概念图:
上图中左侧灰格表示我们代码中使用的当前对象,同时其他白格是未使用对象。(请注意:无疑我的示意图是对实际的简化。实际上,Ruby会用另一个对象来装载字符串"ABC",另一个对象装载Node类定义,还有一个对象装载了代码中分析出的抽象语法树,等等)
如果我们再次调用 Node.new,Ruby将递给我们另一个对象:
这个简单的用链表来预分配对象的算法已经发明了超过50年,而发明人这是赫赫有名的计算机科学家John McCarthy,一开始是用Lisp实现的。Lisp不仅是最早的函数式编程语言,在计算机科学领域也有许多创举。其一就是利用垃圾回收机制自动化进行程序内存管理的概念。
标准版的Ruby,也就是众所周知的"Matz's Ruby Interpreter"(MRI),所使用的GC算法与McCarthy在1960年的实现方式很类似。无论好坏,Ruby的垃圾回收机制已经53岁高龄了。像Lisp一样,Ruby预先创建一些对象,然后在你分配新对象或者变量的时候供你使用。
2.4 Python 的对象分配
我们已经了解了Ruby预先创建对象并将它们存放在可用列表中。那Python又怎么样呢?
尽管由于许多原因Python也使用可用列表(用来回收一些特定对象比如 list),但在为新对象和变量分配内存的方面Python和Ruby是不同的。
例如我们用Pyhon来创建一个Node对象:
与Ruby不同,当创建对象时Python立即向操作系统请求内存。(Python实际上实现了一套自己的内存分配系统,在操作系统堆之上提供了一个抽象层。但是我今天不展开说了。)
当我们创建第二个对象的时候,再次像OS请求内存:
看起来够简单吧,在我们创建对象的时候,Python会花些时间为我们找到并分配内存。
2.5 Ruby 开发者住在凌乱的房间里
Ruby把无用的对象留在内存里,直到下一次GC执行
回过来看Ruby。随着我们创建越来越多的对象,Ruby会持续寻可用列表里取预创建对象给我们。因此,可用列表会逐渐变短:
...然后更短:
请注意我一直在为变量n1赋新值,Ruby把旧值留在原处。"ABC","JKL"和"MNO"三个Node实例还滞留在内存中。Ruby不会立即清除代码中不再使用的旧对象!Ruby开发者们就像是住在一间凌乱的房间,地板上摞着衣服,要么洗碗池里都是脏盘子。作为一个Ruby程序员,无用的垃圾对象会一直环绕着你。
2.6 Python 开发者住在卫生之家庭
用完的垃圾对象会立即被Python打扫干净
Python与Ruby的垃圾回收机制颇为不同。让我们回到前面提到的三个Python Node对象:
在内部,创建一个对象时,Python总是在对象的C结构体里保存一个整数,称为 引用数
。期初,Python将这个值设置为1:
值为1说明分别有个一个指针指向或是引用这三个对象。假如我们现在创建一个新的Node实例,JKL:
与之前一样,Python设置JKL的引用数为1。然而,请注意由于我们改变了n1指向了JKL,不再指向ABC,Python就把ABC的引用数置为0了。 此刻,Python垃圾回收器立刻挺身而出!每当对象的引用数减为0,Python立即将其释放,把内存还给操作系统:
上面Python回收了ABC Node实例使用的内存。记住,Ruby弃旧对象原地于不顾,也不释放它们的内存。
Python的这种垃圾回收算法被称为引用计数。是George-Collins在1960年发明的,恰巧与John McCarthy发明的可用列表算法在同一年出现。就像Mike-Bernstein在6月份哥谭市Ruby大会杰出的垃圾回收机制演讲中说的: "1960年是垃圾收集器的黄金年代..."
Python开发者工作在卫生之家,你可以想象,有个患有轻度OCD(一种强迫症)的室友一刻不停地跟在你身后打扫,你一放下脏碟子或杯子,有个家伙已经准备好把它放进洗碗机了!
现在来看第二例子。加入我们让n2引用n1:
上图中左边的DEF的引用数已经被Python减少了,垃圾回收器会立即回收DEF实例。同时JKL的引用数已经变为了2 ,因为n1和n2都指向它。
2.7 标记-清除
最终那间凌乱的房间充斥着垃圾,再不能岁月静好了。在Ruby程序运行了一阵子以后,可用列表最终被用光光了:
上图中那三个被标M的对象是程序还在使用的。在内部,Ruby实际上使用一串位值,被称为:可用位图(译注:还记得《编程珠玑》里的为突发排序吗,这对离散度不高的有限整数集合具有很强的压缩效果,用以节约机器的资源。),来跟踪对象是否被标记了。
如果说被标记的对象是存活的,剩下的未被标记的对象只能是垃圾,这意味着我们的代码不再会使用它了。我会在下图中用白格子表示垃圾对象:
接下来Ruby清除这些无用的垃圾对象,把它们送回到可用列表中:
在内部这一切发生得迅雷不及掩耳,因为Ruby实际上不会吧对象从这拷贝到那。而是通过调整内部指针,将其指向一个新链表的方式,来将垃圾对象归位到可用列表中的。
现在等到下回再创建对象的时候Ruby又可以把这些垃圾对象分给我们使用了。在Ruby里,对象们六道轮回,转世投胎,享受多次人生。
2.8 标记-删除 vs. 引用计数
乍一看,Python的GC算法貌似远胜于Ruby的:宁舍洁宇而居秽室乎?为什么Ruby宁愿定期强制程序停止运行,也不使用Python的算法呢?
然而,引用计数并不像第一眼看上去那样简单。有许多原因使得不许多语言不像Python这样使用引用计数GC算法:
首先,它不好实现。Python不得不在每个对象内部留一些空间来处理引用数。这样付出了一小点儿空间上的代价。但更糟糕的是,每个简单的操作(像修改变量或引用)都会变成一个更复杂的操作,因为Python需要增加一个计数,减少另一个,还可能释放对象。
第二点,它相对较慢。虽然Python随着程序执行GC很稳健(一把脏碟子放在洗碗盆里就开始洗啦),但这并不一定更快。Python不停地更新着众多引用数值。特别是当你不再使用一个大数据结构的时候,比如一个包含很多元素的列表,Python可能必须一次性释放大量对象。减少引用数就成了一项复杂的递归过程了。
最后,它不是总奏效的。引用计数不能处理环形数据结构--也就是含有循环引用的数据结构。
3. Python中的循环数据结构以及引用计数
3.1 循环引用
通过上篇,我们知道在Python中,每个对象都保存了一个称为引用计数的整数值,来追踪到底有多少引用指向了这个对象。无论何时,如果我们程序中的一个变量或其他对象引用了目标对象,Python将会增加这个计数值,而当程序停止使用这个对象,则Python会减少这个计数值。一旦计数值被减到零,Python将会释放这个对象以及回收相关内存空间。
从六十年代开始,计算机科学界就面临了一个严重的理论问题,那就是针对引用计数这种算法来说,如果一个数据结构引用了它自身,即如果这个数据结构是一个循环数据结构,那么某些引用计数值是肯定无法变成零的。为了更好地理解这个问题,让我们举个例子。下面的代码展示了一些上周我们所用到的节点类:
跟Ruby不同的是,Python中你可以在代码运行的时候动态定义实例变量或对象属性。这看起来似乎有点像Ruby缺失了某些有趣的魔法。(声明下我不是一个Python程序员,所以可能会存在一些命名方面的错误)。我们设置 n1.next 指向 n2,同时设置 n2.prev 指回 n1。现在,我们的两个节点使用循环引用的方式构成了一个双向链表。同时请注意到 ABC 以及 DEF 的引用计数值已经增加到了2。这里有两个指针指向了每个节点:首先是 n1 以及 n2,其次就是 next 以及 prev。
现在,假定我们的程序不再使用这两个节点了,我们将 n1 和 n2 都设置为null(Python中是None)。
好了,Python会像往常一样将每个节点的引用计数减少到1。
3.2 在Python中的零代(Generation Zero)
正如Ruby使用一个链表(free list)来持续追踪未使用的、自由的对象一样,Python使用一种不同的链表来持续追踪活跃的对象。而不将其称之为“活跃列表”,Python的内部C代码将其称为零代(Generation Zero)。每次当你创建一个对象或其他什么值的时候,Python会将其加入零代链表:
从上边可以看到当我们创建ABC节点的时候,Python将其加入零代链表。请注意到这并不是一个真正的列表,并不能直接在你的代码中访问,事实上这个链表是一个完全内部的Python运行时。 相似的,当我们创建DEF节点的时候,Python将其加入同样的链表:
现在零代包含了两个节点对象。(他还将包含Python创建的每个其他值,与一些Python自己使用的内部值。)
3.3 检测循环引用
随后,Python会循环遍历零代列表上的每个对象,找出列表中每个互相引用的对象,根据规则减掉其引用计数。在这个过程中,Python会一个接一个的统计内部引用的数量以防过早地释放对象。
为了便于理解,来看一个例子:
说明:
蓝颜色箭头表示一个变量指向了这个对象,即引用计数为1,如果有2个箭头那么引用计数为2,
如果没有箭头 但是还有引用计数,那么意味着它有循环引用
接下来你会看到Python的GC是如何处理零代链表的。
DEF的引用计数已经变为零了,这意味着收集器可以释放它们并回收内存空间了。剩下的活跃的对象则被移动到一个新的链表:一代链表。
从某种意义上说,Python的GC算法类似于Ruby所用的标记回收算法。周期性地从一个对象到另一个对象追踪引用以确定对象是否还是活跃的,正在被程序所使用的,这正类似于Ruby的标记过程。
Python中的GC阈值
Python什么时候会进行这个标记过程?随着你的程序运行,Python解释器保持对新创建的对象,以及因为引用计数为零而被释放掉的对象的追踪。从理论上说,这两个值应该保持一致,因为程序新建的每个对象都应该最终被释放掉。
当然,事实并非如此。因为循环引用的原因,并且因为你的程序使用了一些比其他对象存在时间更长的对象,从而被分配对象的计数值与被释放对象的计数值之间的差异在逐渐增长。一旦这个差异累计超过某个阈值,则Python的收集机制就启动了,并且触发上边所说到的零代算法,释放“浮动的垃圾”,并且将剩下的对象移动到一代列表。
随着时间的推移,程序所使用的对象逐渐从零代列表移动到一代列表。而Python对于一代列表中对象的处理遵循同样的方法,一旦被分配计数值与被释放计数值累计到达一定阈值,Python会将剩下的活跃对象移动到二代列表。
通过这种方法,你的代码所长期使用的对象,那些你的代码持续访问的活跃对象,会从零代链表转移到一代再转移到二代。通过不同的阈值设置,Python可以在不同的时间间隔处理这些对象。Python处理零代最为频繁,其次是一代然后才是二代。
垃圾回收(三)-gc模块
一.垃圾回收机制
Python中的垃圾回收是以引用计数为主,分代收集为辅。
1、导致引用计数+1的情况
对象被创建,例如a=23
对象被引用,例如b=a
对象被作为参数,传入到一个函数中,例如func(a)
对象作为一个元素,存储在容器中,例如list1=[a,a]
2、导致引用计数-1的情况
对象的别名被显式销毁,例如del a
对象的别名被赋予新的对象,例如a=24
一个对象离开它的作用域,例如f函数执行完毕时,func函数中的局部变量(全局变量不会)
对象所在的容器被销毁,或从容器中删除对象
3、查看一个对象的引用计数
import sys
a = "hello world"
sys.getrefcount(a)
可以查看a对象的引用计数,但是比正常计数大1,因为调用函数的时候传入a,这会让a的引用计数+1
二.循环引用导致内存泄露
内存泄漏
申请了某些内存,但是忘记了释放,那么这就造成了内存的浪费,久而久之内存就不够用了
1. 让程序产生内存泄漏
import gc class ClassA(): def __init__(self): print('object born,id:%s'%str(id(self))) def f2(): while True: c1 = ClassA() c2 = ClassA() c1.t = c2 c2.t = c1 del c1 del c2 #python默认是开启垃圾回收的,可以通过下面代码来将其关闭 gc.disable() f2()
执行f2(),进程占用的内存会不断增大。
- 创建了c1,c2后这两块内存的引用计数都是1,执行
c1.t=c2
和c2.t=c1
后,这两块内存的引用计数变成2. - 在del c1后,引用计数变为1,由于不是为0,所以c1对象不会被销毁;同理,c2对象的引用数也是1。
- python默认是开启垃圾回收功能的,但是由于以上程序已经将其关闭,因此导致垃圾回收器都不会回收它们,所以就会导致内存泄露。
三.垃圾回收
class ClassA(): def __init__(self): print('object born,id:%s'%str(id(self))) def f2(): while True: c1 = ClassA() c2 = ClassA() c1.t = c2 c2.t = c1 del c1 del c2 gc.collect()#手动调用垃圾回收功能,这样在自动垃圾回收被关闭的情况下,也会进行回收 #python默认是开启垃圾回收的,可以通过下面代码来将其关闭 gc.disable() f2()
有三种情况会触发垃圾回收:
- 当gc模块的计数器达到阀值的时候,自动回收垃圾
- 调用gc.collect(),手动回收垃圾
- 程序退出的时候,python解释器来回收垃圾
四. gc模块的自动垃圾回收触发机制
在Python中,采用分代收集的方法。把对象分为三代,一开始,对象在创建的时候,放在一代中,如果在一次一代的垃圾检查中,改对象存活下来,就会被放到二代中,同理在一次二代的垃圾检查中,该对象存活下来,就会被放到三代中。
gc模块里面会有一个长度为3的列表的计数器,可以通过gc.get_count()获取。
例如(488,3,0),其中488是指距离上一次一代垃圾检查,Python分配内存的数目减去释放内存的数目,注意是内存分配,而不是引用计数的增加。例如:
print gc.get_count() # (590, 8, 0) a = ClassA() print gc.get_count() # (591, 8, 0) del a print gc.get_count() # (590, 8, 0)
3是指距离上一次二代垃圾检查,一代垃圾检查的次数,同理,0是指距离上一次三代垃圾检查,二代垃圾检查的次数。
gc模快有一个自动垃圾回收的阀值
,即通过gc.get_threshold函数获取到的长度为3的元组,例如(700,10,10) 每一次计数器的增加,gc模块就会检查增加后的计数是否达到阀值的数目,如果是,就会执行对应的代数的垃圾检查,然后重置计数器
例如,假设阀值是(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)
内建属性
"teachclass.py" class Person(object): pass
python3.5中类的内建属性和方法
经典类(旧式类),早期如果没有要继承的父类,继承里空着不写的类
#py2中无继承父类,称之经典类,py3中已默认继承object class Person: pass
子类没有实现__init__
方法时,默认自动调用父类的。 如定义__init__
方法时,需自己手动调用父类的__init__
方法
常用专有属性 | 说明 | 触发方式 |
---|---|---|
__init__ |
构造初始化函数 | 创建实例后,赋值时使用,在__new__ 后 |
__new__ |
生成实例所需属性 | 创建实例时 |
__class__ |
实例所在的类 | 实例.__class__ |
__str__ |
实例字符串表示,可读性 | print(类实例),如没实现,使用repr结果 |
__repr__ |
实例字符串表示,准确性 | 类实例 回车 或者 print(repr(类实例)) |
__del__ |
析构 | del删除实例 |
__dict__ |
实例自定义属性 | vars(实例.__dict__) |
__doc__ |
类文档,子类不继承 | help(类或实例) |
__getattribute__ |
属性访问拦截器 | 访问实例属性时 |
__bases__ |
类的所有父类构成元素 | 类名.__bases__ |
__getattribute__
例子:
class Itcast(object): def __init__(self,subject1): self.subject1 = subject1 self.subject2 = 'cpp' #属性访问时拦截器,打log def __getattribute__(self,obj): if obj == 'subject1': print('log subject1') return 'redirect python' else: #测试时注释掉这2行,将找不到subject2 return object.__getattribute__(self,obj) def show(self): print('this is Itcast') s = Itcast("python") print(s.subject1) print(s.subject2)
运行结果:
log subject1 redirect python cpp
__getattribute__的坑
class Person(object): def __getattribute__(self,obj): print("---test---") if obj.startswith("a"): return "hahha" else: return self.test def test(self): print("heihei") t.Person() t.a #返回hahha t.b #会让程序死掉 #原因是:当t.b执行时,会调用Person类中定义的__getattribute__方法,但是在这个方法的执行过程中 #if条件不满足,所以 程序执行else里面的代码,即return self.test 问题就在这,因为return 需要把 #self.test的值返回,那么首先要获取self.test的值,因为self此时就是t这个对象,所以self.test就是 #t.test 此时要获取t这个对象的test属性,那么就会跳转到__getattribute__方法去执行,即此时产 #生了递归调用,由于这个递归过程中 没有判断什么时候推出,所以这个程序会永无休止的运行下去,又因为 #每次调用函数,就需要保存一些数据,那么随着调用的次数越来越多,最终内存吃光,所以程序 崩溃 # # 注意:以后不要在__getattribute__方法中调用self.xxxx
内建函数
Build-in Function,启动python解释器,输入dir(__builtin__)
, 可以看到很多python解释器启动后默认加载的属性和函数,这些函数称之为内建函数, 这些函数因为在编程时使用较多,cpython解释器用c语言实现了这些函数,启动解释器 时默认加载。
这些函数数量众多,不宜记忆,开发时不是都用到的,待用到时再help(function), 查看如何使用,或结合百度查询即可,在这里介绍些常用的内建函数。
range
range(stop) -> list of integers range(start, stop[, step]) -> list of integers
-
start:计数从start开始。默认是从0开始。例如range(5)等价于range(0, 5);
-
stop:到stop结束,但不包括stop.例如:range(0, 5) 是[0, 1, 2, 3, 4]没有5
-
step:每次跳跃的间距,默认为1。例如:range(0, 5) 等价于 range(0, 5, 1)
python2中range返回列表,python3中range返回一个迭代值。如果想得到列表,可通过list函数
a = range(5) list(a)
创建列表的另外一种方法
In [21]: testList = [x+2 for x in range(5)] In [22]: testList Out[22]: [2, 3, 4, 5, 6]
map函数
map函数会根据提供的函数对指定序列做映射
map(...) map(function, sequence[, sequence, ...]) -> list
- function:是一个函数
- sequence:是一个或多个序列,取决于function需要几个参数
- 返回值是一个list
参数序列中的每一个元素分别调用function函数,返回包含每次function函数返回值的list。
#函数需要一个参数 map(lambda x: x*x, [1, 2, 3]) #结果为:[1, 4, 9] #函数需要两个参数 map(lambda x, y: x+y, [1, 2, 3], [4, 5, 6]) #结果为:[5, 7, 9] def f1( x, y ): return (x,y) l1 = [ 0, 1, 2, 3, 4, 5, 6 ] l2 = [ 'Sun', 'M', 'T', 'W', 'T', 'F', 'S' ] l3 = map( f1, l1, l2 ) print(list(l3)) #结果为:[(0, 'Sun'), (1, 'M'), (2, 'T'), (3, 'W'), (4, 'T'), (5, 'F'), (6, 'S')]
filter函数
filter函数会对指定序列执行过滤操作
filter(...) filter(function or None, sequence) -> list, tuple, or string Return those items of sequence for which function(item) is true. If function is None, return the items that are true. If sequence is a tuple or string, return the same type, else return a list.
- function:接受一个参数,返回布尔值True或False
- sequence:序列可以是str,tuple,list
filter函数会对序列参数sequence中的每个元素调用function函数,最后返回的结果包含调用结果为True的元素。
返回值的类型和参数sequence的类型相同
filter(lambda x: x%2, [1, 2, 3, 4]) [1, 3] filter(None, "she") 'she'
reduce函数
reduce函数,reduce函数会对参数序列中元素进行累积
reduce(...) reduce(function, sequence[, initial]) -> value Apply a function of two arguments cumulatively to the items of a sequence, from left to right, so as to reduce the sequence to a single value. For example, reduce(lambda x, y: x+y, [1, 2, 3, 4, 5]) calculates ((((1+2)+3)+4)+5). If initial is present, it is placed before the items of the sequence in the calculation, and serves as a default when the sequence is empty.
- function:该函数有两个参数
- sequence:序列可以是str,tuple,list
- initial:固定初始值
reduce依次从sequence中取一个元素,和上一次调用function的结果做参数再次调用function。 第一次调用function时,如果提供initial参数,会以sequence中的第一个元素和initial 作为参数调用function,否则会以序列sequence中的前两个元素做参数调用function。 注意function函数不能为None。
reduce(lambda x, y: x+y, [1,2,3,4]) 10 reduce(lambda x, y: x+y, [1,2,3,4], 5) 15 reduce(lambda x, y: x+y, ['aa', 'bb', 'cc'], 'dd') 'ddaabbcc'
在Python3里,reduce函数已经被从全局名字空间里移除了, 它现在被放置在fucntools模块里用的话要先引入: from functools import reduce
集合set
集合与之前列表、元组类似,可以存储多个数据,但是这些数据是不重复的
集合对象还支持union(联合), intersection(交), difference(差)和sysmmetric_difference(对称差集)等数学运算.
>>> x = set('abcd') >>> x {'c', 'a', 'b', 'd'} >>> type(x) <class 'set'> >>> >>> >>> y = set(['h','e','l','l','o']) >>> y {'h', 'e', 'o', 'l'} >>> >>> >>> z = set('spam') >>> z {'s', 'a', 'm', 'p'} >>> >>> >>> y&z #交集 set() >>> >>> >>> x&z #交集 {'a'} >>> >>> >>> x|y #并集 {'a', 'e', 'd', 'l', 'c', 'h', 'o', 'b'} >>> >>> x-y #差集 {'c', 'a', 'b', 'd'} >>> >>> >>> x^z #对称差集(在x或z中,但不会同时出现在二者中) {'m', 'd', 's', 'c', 'b', 'p'} >>> >>> >>> len(x) 4 >>> len(y) 4 >>> len(z) 4 >>>
模块进阶
Python有一套很有用的标准库(standard library)。标准库会随着Python解释器,一起安装在你的电脑中的。 它是Python的一个组成部分。这些标准库是Python为你准备好的利器,可以让编程事半功倍。
常用标准库
标准库 | 说明 |
---|---|
builtins | 内建函数默认加载 |
os | 操作系统接口 |
sys | Python自身的运行环境 |
functools | 常用的工具 |
json | 编码和解码 JSON 对象 |
logging | 记录日志,调试 |
multiprocessing | 多进程 |
threading | 多线程 |
copy | 拷贝 |
time | 时间 |
datetime | 日期和时间 |
calendar | 日历 |
hashlib | 加密算法 |
random | 生成随机数 |
re | 字符串正则匹配 |
socket | 标准的 BSD Sockets API |
shutil | 文件和目录管理 |
glob | 基于文件通配符搜索 |
hashlib
import hashlib m = hashlib.md5() #创建hash对象,md5:(message-Digest Algorithm 5)消息摘要算法,得出一个128位的密文 print m #<md5 HASH object> m.update('itcast') #更新哈希对象以字符串参数 print m.hexdigest() #返回十六进制数字字符串
应用实例
用于注册、登录....
import hashlib import datetime KEY_VALUE = 'Itcast' now = datetime.datetime.now() m = hashlib.md5() str = '%s%s' % (KEY_VALUE,now.strftime("%Y%m%d")) m.update(str.encode('utf-8')) value = m.hexdigest() print(value)
运行结果:
8ad2d682e3529dac50e586fee8dc05c0
更多标准库
http://python.usyiyi.cn/translate/python_352/library/index.html
常用扩展库
扩展库 | 说明 |
---|---|
requests | 使用的是 urllib3,继承了urllib2的所有特性 |
urllib | 基于http的高层库 |
scrapy | 爬虫 |
beautifulsoup4 | HTML/XML的解析器 |
celery | 分布式任务调度模块 |
redis | 缓存 |
Pillow(PIL) | 图像处理 |
xlsxwriter | 仅写excle功能,支持xlsx |
xlwt | 仅写excle功能,支持xls ,2013或更早版office |
xlrd | 仅读excle功能 |
elasticsearch | 全文搜索引擎 |
pymysql | 数据库连接库 |
mongoengine/pymongo | mongodbpython接口 |
matplotlib | 画图 |
numpy/scipy | 科学计算 |
django/tornado/flask | web框架 |
xmltodict | xml 转 dict |
SimpleHTTPServer | 简单地HTTP Server,不使用Web框架 |
gevent | 基于协程的Python网络库 |
fabric | 系统管理 |
pandas | 数据处理库 |
scikit-learn | 机器学习库 |
就可以运行起来静态服务。平时用它预览和下载文件太方便了。
在终端中输入命令:
python2中
python -m SimpleHTTPServer PORT
python3中
python -m http.server PORT
读写excel文件
1.安装个easy_install工具
sudo apt-get install python-setuptools
2.安装模块
sudo easy_install xlrd sudo easy_install xlwt
matplotlib
调试
pdb
pdb是基于命令行的调试工具,非常类似gnu的gdb(调试c/c++)。
命令 | 简写命令 | 作用 |
---|---|---|
break | b | 设置断点 |
continue | c | 继续执行程序 |
list | l | 查看当前行的代码段 |
step | s | 进入函数 |
return | r | 执行代码直到从当前函数返回 |
quit | q | 中止并退出 |
next | n | 执行下一行 |
p | 打印变量的值 | |
help | h | 帮助 |
args | a | 查看传入参数 |
回车 | 重复上一条命令 | |
break | b | 显示所有断点 |
break lineno | b lineno | 在指定行设置断点 |
break file:lineno | b file:lineno | 在指定文件的行设置断点 |
clear num | 删除指定断点 | |
bt | 查看函数调用栈帧 |
编码风格
错误认知
- 这很浪费时间
- 我是个艺术家
- 所有人都能穿的鞋不会合任何人的脚
- 我善长制定编码规范
正确认知
- 促进团队合作
- 减少bug处理
- 提高可读性,降低维护成本
- 有助于代码审查
- 养成习惯,有助于程序员自身的成长
pep8 编码规范
Python Enhancement Proposals :python改进方案
https://www.python.org/dev/peps/
pep8 官网规范地址
https://www.python.org/dev/peps/pep-0008/
Guido的关键点之一是:代码更多是用来读而不是写。编码规范旨在改善Python代码的可读性。
风格指南强调一致性。项目、模块或函数保持一致都很重要。
每级缩进用4个空格。
括号中使用垂直隐式缩进或使用悬挂缩进。后者应该注意第一行要没有参数,后续行要有缩进。
- Yes
# 对准左括号 foo = long_function_name(var_one, var_two, var_three, var_four) # 不对准左括号,但加多一层缩进,以和后面内容区别。 def long_function_name( var_one, var_two, var_three, var_four): print(var_one) # 悬挂缩进必须加多一层缩进. foo = long_function_name( var_one, var_two, var_three, var_four)
- No
# 不使用垂直对齐时,第一行不能有参数。 foo = long_function_name(var_one, var_two, var_three, var_four) # 参数的缩进和后续内容缩进不能区别。 def long_function_name( var_one, var_two, var_three, var_four): print(var_one)
4个空格的规则是对续行可选的。
# 悬挂缩进不一定是4个空格 foo = long_function_name( var_one, var_two, var_three, var_four) if语句跨行时,两个字符关键字(比如if)加上一个空格,再加上左括号构成了很好的缩进。后续行暂时没有规定,至少有如下三种格式,建议使用第3种。 # 没有额外缩进,不是很好看,个人不推荐. if (this_is_one_thing and that_is_another_thing): do_something() # 添加注释 if (this_is_one_thing and that_is_another_thing): # Since both conditions are true, we can frobnicate. do_something() # 额外添加缩进,推荐。 # Add some extra indentation on the conditional continuation line. if (this_is_one_thing and that_is_another_thing): do_something()
右边括号也可以另起一行。有两种格式,建议第2种。
# 右括号不回退,个人不推荐 my_list = [ 1, 2, 3, 4, 5, 6, ] result = some_function_that_takes_arguments( 'a', 'b', 'c', 'd', 'e', 'f', ) # 右括号回退 my_list = [ 1, 2, 3, 4, 5, 6, ] result = some_function_that_takes_arguments( 'a', 'b', 'c', 'd', 'e', 'f', )
空格或Tab?
- 空格是首选的缩进方法。
- Tab仅仅在已经使用tab缩进的代码中为了保持一致性而使用。
- Python 3中不允许混合使用Tab和空格缩进。
- Python 2的包含空格与Tab和空格缩进的应该全部转为空格缩进。
最大行宽
- 限制所有行的最大行宽为79字符。
- 文本长块,比如文档字符串或注释,行长度应限制为72个字符。
空行
- 两行空行分割顶层函数和类的定义。
- 类的方法定义用单个空行分割。
- 额外的空行可以必要的时候用于分割不同的函数组,但是要尽量节约使用。
- 额外的空行可以必要的时候在函数中用于分割不同的逻辑块,但是要尽量节约使用。
源文件编码
- 在核心Python发布的代码应该总是使用UTF-8(ASCII在Python 2)。
- Python 3(默认UTF-8)不应有编码声明。
导入在单独行
Yes:
import os import sys from subprocess import Popen, PIPE
No:
import sys, os
-
导入始终在文件的顶部,在模块注释和文档字符串之后,在模块全局变量和常量之前。
- 导入顺序如下:标准库进口,相关的第三方库,本地库。各组的导入之间要有空行。
禁止使用通配符导入。
通配符导入(from import *)应该避免,因为它不清楚命名空间有哪些名称存,混淆读者和许多自动化的工具。
字符串引用
- Python中单引号字符串和双引号字符串都是相同的。注意尽量避免在字符串中的反斜杠以提高可读性。
- 根据PEP 257, 三个引号都使用双引号。
括号里边避免空格
# 括号里边避免空格 # Yes spam(ham[1], {eggs: 2}) # No spam( ham[ 1 ], { eggs: 2 } )
逗号,冒号,分号之前避免空格
# 逗号,冒号,分号之前避免空格 # Yes if x == 4: print x, y; x, y = y, x # No if x == 4 : print x , y ; x , y = y , x
索引操作中的冒号当作操作符处理前后要有同样的空格(一个空格或者没有空格,个人建议是没有。)
# Yes ham[1:9], ham[1:9:3], ham[:9:3], ham[1::3], ham[1:9:] ham[lower:upper], ham[lower:upper:], ham[lower::step] ham[lower+offset : upper+offset] ham[: upper_fn(x) : step_fn(x)], ham[:: step_fn(x)] ham[lower + offset : upper + offset] # No ham[lower + offset:upper + offset] ham[1: 9], ham[1 :9], ham[1:9 :3] ham[lower : : upper] ham[ : upper]
函数调用的左括号之前不能有空格
# Yes spam(1) dct['key'] = lst[index] # No spam (1) dct ['key'] = lst [index]
赋值等操作符前后不能因为对齐而添加多个空格
# Yes x = 1 y = 2 long_variable = 3 # No x = 1 y = 2 long_variable = 3
二元运算符两边放置一个空格
涉及 =、符合操作符 ( += , -=等)、比较( == , < , > , != , <> , <= , >= , in , not in , is , is not )、布尔( and , or , not )。
优先级高的运算符或操作符的前后不建议有空格。
# Yes i = i + 1 submitted += 1 x = x*2 - 1 hypot2 = x*x + y*y c = (a+b) * (a-b) # No i=i+1 submitted +=1 x = x * 2 - 1 hypot2 = x * x + y * y c = (a + b) * (a - b)
关键字参数和默认值参数的前后不要加空格
# Yes def complex(real, imag=0.0): return magic(r=real, i=imag) # No def complex(real, imag = 0.0): return magic(r = real, i = imag)
通常不推荐复合语句(Compound statements: 多条语句写在同一行)。
# Yes if foo == 'blah': do_blah_thing() do_one() do_two() do_three() # No if foo == 'blah': do_blah_thing() do_one(); do_two(); do_three()
尽管有时可以在if/for/while 的同一行跟一小段代码,但绝不要跟多个子句,并尽量避免换行。
# No if foo == 'blah': do_blah_thing() for x in lst: total += x while t < 10: t = delay() 更不是: # No if foo == 'blah': do_blah_thing() else: do_non_blah_thing() try: something() finally: cleanup() do_one(); do_two(); do_three(long, argument, list, like, this) if foo == 'blah': one(); two(); three()
避免采用的名字
决不要用字符'l'(小写字母el),'O'(大写字母oh),或 'I'(大写字母eye) 作为单个字符的变量名。一些字体中,这些字符不能与数字1和0区别。用'L' 代替'l'时。
包和模块名
模块名要简短,全部用小写字母,可使用下划线以提高可读性。包名和模块名类似,但不推荐使用下划线。