python内存管理和垃圾回收机制
数据类型
- 整数:十进制;十六进制(‘0x’)
- 浮点数:-1.2 ;1.2e-5
整数和浮点数在计算机内部存储的方式是不同的,整数运算永远是精确的(除法难道也是精确的?是的!),而浮点数运算则可能会有四舍五入的误差。
- 字符串:
- 转义字符:’/’
- 用r’‘表示’'内部的字符串默认不转义
- 如果字符串内部有很多换行,用\n写在一行里不好阅读,为了简化,Python允许用’’’…’’'的格式表示多行内容
- 布尔值:True False;运算 and、or和not
- 空值:Python里一个特殊的值,用None表示。None不能理解为0,因为0是有意义的,而None是一个特殊的空值
- 变量:在定义变量时不必指定变量类型的动态语言。原因:变量并非地址,而是指向地址的引用
- 常量
list
# 创建列表
#只要把逗号分隔的不同的数据项使用方括号([ ])括起来即可
#下标(角标,索引)从0开始,最后一个元素的下标可以写-1
list = ['1',‘2,‘Jack’]
list = [] #空列表
#添加新的元素
list.append() # 末尾增加一个元素
list.insert(n,'4') #在指定位置添加元素,如果指定的下标不存在,那么就是在末尾添加
list1.extend(list2) #合并两个list,list2可为任意可迭代对象
#查看列表中的值
print(list) 遍历列表
#等价于
#for i in list:
#print(i)
print(list[n]) #使用下标索引来访问列表中的值,同样也可以使用方括号的形式截取字符
#删除list 中的元素
list.pop() #删最后一个元素
list.pop(n)#指定下标,删除指定的元素,如果删除一个不存在的元素会报错,有返回值
list.remove(xx) #删除list 里面的一个元素,有多个相同的元素,删除第一个 ,无返回值
list.clear() #清空列表
del list[n] #删除指定下标对应的元素
del list #删除整个列表, list删除后无法访问
#排序和反转
list.reverse() #将列表反转
list.sort()#排序,默认升序
list.sort(reverse=True) #降序排列
#列表操作的函数
len(list) #列表元素个数
max(list) #返回列表元素最大值
min(list) #返回列表元素最小值
list(seq) #将元组转换为列表
for i, v in enumerate(list) # 用法(打印元素对应的下标)
list.count(xx) #查看某个元素在这个列表里的个数,如果改元素不存在,那么返回0
list.index(xx)#找到这个元素的小标,如果有多个,返回第一个,如果找一个不存在的元素会报错
- 注
- list 中有字符串,数字时不能排序,排序针对同类型
- list元素可以不同类型,甚至是另一个list
tuple
tuple = ('1',‘2,‘Jack’)
- 一旦初始化就不可更改,更加安全
- (5):int类型
- (5,):tuple类型
dict
- 要保证hash可以计算,key必须是不可变类型
#创建
dict = {"a" : "apple", "b" : "banana", "g" : "grape", "o" : "orange"}
d = dict(name='visaya', age=21)
d2 = dict(zip(['a', 'b'], [1, 2]))
#添加
dict["w"] = "watermelon"
#删除
del(dict["a"])
dict.pop("b")#有返回值
dict.clear()
#修改
dict["g"] = "grapefruit"
dict2 = {"c" : "grape", "d" : "orange"}
dict.update(dict2)
#遍历
dict.keys() #因为不存在重复的项,类似集合可以进行交集和并集等集合操作,但dict.values()不可以进行如上操作。
dict.values()
dict.items()
dict.iterkeys()
dict.itervalues()
dict.iteritems()
print(dict)
for k in dict:
print("dict[%s] =" % k,dict[k])
#items():列表每个元素是一个key和value组成的元组,以列表的方式输出
print(dict.items())
for (k, v) in dict.items():
print("dict[%s] =" % k, v)
#iteritems()
print(dict.iteritems())
for k, v in dict.iteritems():
print("dict[%s] =" % k, v)
for (k, v) in zip(dict.iterkeys(), dict.itervalues()):
print("dict[%s] =" % k, v(
#判断key是否存在
"a" in dict #返回True
dict.get("c")#不存在时返回None
dict.get("c",-1)#不存在时返回指定值(本例中设为-1)
#设置默认值
dict = {}
dict.setdefault("a")
print(dict) #{'a':None}
#调用sorted()排序
#按照key排序
print sorted(dict.items(), key=lambda d: d[0])
#按照value排序
print sorted(dict.items(), key=lambda d: d[1])
#字典的浅拷贝
#字典的深拷贝
#排序字典键
Ks = list(d.keys())
Ks.sort()
for k in Ks:
print(k, d[k])
for k in sorted(d.keys()):
print(k, d[k])
#注意
for k in list(d.keys()).sort():
print(k, d[k])
#出错,因为list.sort()没有返回值
# 常用函数
get()
pop()
udpate()
del()
clear()
copy()
set
s = set([1,2,3,'bob'])
s = {1,2,3,'Bob'}
add()
remove()
可以做数学上的集合运算:
- 交:s1 & s2
- 并:s1 | s2
- 差:s1 - s2
- 异或:s1 ^ s2
其他
- 不可变数据类型
- 数值类型(int、float、bool)、string(字符串)、tuple(元组)
- 值变则其对应的地址也变化
- 元组:元组内可以存储 数值类型、字符串、列表、元组,一经创建,内部的元素的值就不能修改,否则报错。但是我们可以对元组进行连接组合,组合后地址改变。
- 可变数据类型
- list(列表)、dict(字典)、set(集合,不常用)
- 值变但是地址不变
字符串
编码
- ASCII编码:大小写英文字母、数字和一些符号
- Unicode编码:把所有语言都统一到一套编码里
- UTF-8编码:本着节约的精神,把Unicode编码转化为“可变长编码”的UTF-8编码
- 在计算机内存中,统一使用Unicode编码,当需要保存到硬盘或者需要传输的时候,就转换为UTF-8编码。
其他
- / 的作用:转义;路径连接;代码太长转行
内存管理和垃圾回收
1. 引用计数为主,标记回收、分代回收为辅
1. 引用计数
引用计数是一种非常高效的内存管理手段, 当一个 Python 对象被引用时其引用计数增加 1, 当其不再被一个变量引用时则计数减 1. 当引用计数等于 0 时对象被删除。
- 引用计数增加
- .对象被创建:x=4
- 另外的别名被创建:y=x
- .被作为参数传递给函数:foo(x)
- 作为容器对象的一个元素:a=[‘1’,x,‘25’]
- 引用计数减少
- 一个本地引用离开了它的作用域。比如上面的foo(x)函数结束时,x指向的对象引用减1。
- 对象的别名被显式的销毁:del x ;或者del y
- 对象的一个别名被赋值给其他对象:x=789
- 对象从一个窗口对象中移除:myList.remove(x)
- 窗口对象本身被销毁:del myList,或者窗口对象本身离开了作用域。
- 缺点:不能解决循环引用(主要出现在可变的容器对象,不可变的数值对象不可能出现此问题)
A和B相互引用而且没有外部引用A与B中的任何一个。也就是对象之间互相应用,导致引用链形成一个环。
>>>a = { } #对象A的引用计数为 1
>>>b = { } #对象B的引用计数为 1
>>>a['b'] = b #B的引用计数增1
>>>b['a'] = a #A的引用计数增1
>>>del a #A的引用减 1,最后A对象的引用为 1
>>>del b #B的引用减 1, 最后B对象的引用为 1
执行 del 后,A、B对象已经没有任何引用指向这两个对象,但是这两个对象各包含一个对方对象的引用,虽然最后两个对象都无法通过其它变量来引用这两个对象了,这对GC来说就是两个非活动对象或者说是垃圾对象。理论上是需要被回收的。 按上面的引用计数原理,要计数为0才会回收,但是他们的引用计数并没有减少到零。因此如果是使用引用计数法来管理这两对象的话,他们并不会被回收,它会一直驻留在内存中,就会造成了内存泄漏(内存空间在使用完毕后未释放)。
2. 标记清除
- 标记清除算法是一种基于追踪回收(tracing GC)技术实现的垃圾回收算法。 它分为两个阶段:第一阶段是标记阶段,GC会把所有的 活动对象 打上标记,第二阶段是把那些 非活动对象(没有标记的对象) 进行回收。那么GC又是如何判断哪些是活动对象哪些是非活动对象的呢?
- 对象之间通过引用(指针)连在一起,构成一个有向图,对象构成这个有向图的节点,而引用关系构成这个有向图的边。从**根对象(root object)**出发,沿着有向边遍历对象,可达的(reachable)对象标记为活动对象,不可达的对象就是要被清除的非活动对象。根对象就是全局变量、调用栈、寄存器。
- 缺点:清除非活动的对象前它必须顺序扫描整个堆内存,哪怕只剩下小部分活动对象也要扫描所有对象
3. 分代回收
从前面“标记-清除”这样的垃圾收集机制来看,这种垃圾收集机制所带来的额外操作实际上与系统中总的内存块的数量是相关的,当需要回收的内存块越多时,垃圾检测带来的额外操作就越多,而垃圾回收带来的额外操作就越少;反之,当需回收的内存块越少时,垃圾检测就将比垃圾回收带来更少的额外操作。
Python将内存根据对象的存活时间划分为不同的集合,每个集合称为一个代,Python将内存分为了3“代”,分别为年轻代(第0代)、中年代(第1代)、老年代(第2代),他们对应的是3个链表,它们的垃圾收集频率与对象的存活时间的增大而减小。新创建的对象都会分配在年轻代,年轻代链表的总数达到上限时,Python垃圾收集机制就会被触发,把那些可以被回收的对象回收掉,而那些不会回收的对象就会被移到中年代去,依此类推,老年代中的对象是存活时间最久的对象,甚至是存活于整个系统的生命周期内。同时,分代回收是建立在标记清除技术基础之上。
2. 内存池
在Python中,许多时候申请的内存都是小块的内存,这些小块内存在申请后,很快又会被释放,由于这些内存的申请并不是为了创建对象,所以并没有对象一级的内存池机制。这就意味着Python在运行期间会大量地执行malloc和free的操作,频繁地在用户态和核心态之间进行切换,这将严重影响Python的执行效率。为了加速Python的执行效率,Python引入了一个内存池机制,用于管理对小块内存的申请和释放
- Python 的内存机制呈现金字塔形状,-1,-2 层主要由操作系统进行操作;
- 第 0 层是 C 中的 malloc,free 等内存分配和释放函数进行操作;
- 第 1 层和第 2 层是内存池,有 Python 的接口函数 PyMem_Malloc 函数实现,当对象小于256K 时有该层直接分配内存;
- 第 3 层是最上层,也就是我们对 Python 对象的直接操作;
- Python 内部默认的小块内存与大块内存的分界点定在 256 个字节,当申请的内存小于 256 字节时,PyObject_Malloc 会在内存池中申请内存;当申请的内存大于 256 字节时,PyObject_Malloc 的行为将蜕化为 malloc 的行为。当然,通过修改 Python 源代码,我们可以改变这个默认值,从而改变 Python 的默认内存管理行为。
3. 内存泄漏
- 指由于疏忽或错误造成程序未能释放已经不再使用的内存的情况。内存泄漏并非指内存在物理上的消失,而是应用程序分配某段内存后,由于设计错误,失去了对该段内存的控制,因而造成了内存的浪费。导致程序运行速度减慢甚至系统崩溃等严重后果。
- 有 __ del__() 函数的对象间的循环引用是导致内存泄漏的主凶。
- 不使用一个对象时使用:del object 来删除一个对象的引用计数就可以有效防止内存泄漏问题。
- 可以通过 sys.getrefcount(obj) 来获取对象的引用计数,并根据返回值是否为 0 来判断是否内存泄漏。
函数
1. 调用
1. 类型转化
- int() float() str() bool()
- set、list、tuple、dict相互转化
- list()、set()、tuple():字典转为其他时只有key
- dict():其他三个转过来时都报错
- 但是当list、set、tuple里面的元素是元组(列表),元组(列表)中的元素必须是2个,就能转为字典)
print(dict({(1, 2), (3, 4)})) = {1: 2, 3: 4}
print(dict(((1, 2), (3, 4)))) = {1: 2, 3: 4}
print(dict([(1, 2), (3, 4)])) = {1: 2, 3: 4}
2. 定义
-返回值
* 无返回值其实是return None
* 函数返回多值是以tuple的形式
1. pass
- 没想好怎么写的时候可以用来在函数中占位,保证语法正确
- continue直接跳到下一个循环,pass则是占位,虽然什么都不会发生,但是如果后面语句的话这些语句会继续运行
3. 函数的参数
1. 位置参数
2. 默认参数
- 必选参数在前,默认参数在后,否则Python的解释器会报错
- 默认参数降低调用函数的难度
- 函数的默认参数是可变类型,它只在函数定义的那一刻创建一次
- 定义默认参数要牢记一点:默认参数必须指向不变对象。如None
3. 可变参数:
#*args:位置参数,不知道向函数传递多少参数时,传递一个列表或元组,元素个数可为0
def calc(*numbers):
sum = 0
for n in numbers:
sum = sum + n * n
return sum
calc(1, 2, 3, 4)
# **如果已经有一个list或者tuple,要调用一个可变参数怎么办?**
nums = [1, 2, 3]
#*nums表示把nums这个list的所有元素作为可变参数传进去。
calc(*nums)
4. 关键字参数
#- **kw:在*args前,传递一个字典,形式:key1 = value1,key2=value2...
def person(name, age, **kw):
print('name:', name, 'age:', age, 'other:', kw)
person('Adam', 45, gender='M', job='Engineer')
extra = {'city': 'Beijing', 'job': 'Engineer'}
#**extra表示把extra这个dict的所有key-value用关键字参数传入到函数的**kw参数
#kw将获得一个dict
#注意kw获得的dict是extra的一份拷贝,对kw的改动不会影响到函数外的extra。
person('Jack', 24, **extra)
5. 命名关键字参数
命名关键字参数必须传入参数名,这和位置参数不同。如果没有传入参数名,调用将报错:
#命名关键字参数需要一个特殊分隔符*,*后面的参数被视为命名关键字参数
def person(name, age, *, city, job):
print(name, age, city, job)
# 如果函数定义中已经有了一个可变参数,后面跟着的命名关键字参数就不再需要一个特殊分隔符*了
def person(name, age, *args, city, job):
print(name, age, args, city, job)
#命名关键字参数可以有缺省值,从而简化调用:
def person(name, age, *, city='Beijing', job):
print(name, age, city, job)
6. 参数组合
顺序:必选参数、默认参数、可变参数、命名关键字参数和关键字参数
面向对象
1. 类和实例
- 和静态语言不同,Python允许对实例变量绑定任何数据,也就是说,对于两个实例变量,虽然它们都是同一个类的不同实例,但拥有的变量名称都可能不同:
bart = Student('Bart Simpson', 59)
lisa = Student('Lisa Simpson', 87)
bart.age = 8
print(lisa.age) # 报错
2.访问限制
- __xxx: 私有变量(private),只有内部可以访问,外部不能访问
- __ xxx__:是特殊变量,特殊变量是可以直接访问的,不是private变量,所以,不能用__name__、__score__这样的变量名
- _xxx:“虽然我可以被访问,但是,请把我视为私有变量,不要随意访问”
1. 注
- __xxx是不是一定不能从外部访问呢?
- 其实也不是。不能直接访问__xxx是因为Python解释器对外把__xxx变量改成了_Student__xxx,所以,仍然可以通过_Student__xxx来访问__xxx变量
- 不同版本的Python解释器可能会把__xxx改成不同的变量名。
- 表面上看,外部代码“成功”地设置了__name变量,但实际上这个__name变量和class内部的__name变量不是一个变量!内部的__name变量已经被Python解释器自动改成了_Student__name,而外部代码给bart新增了一个__name变量
bart = Student('Bart Simpson', 59)
bart.get_name() #'Bart Simpson'
bart.__name = 'New Name' # 设置__name变量!
bart.__name # 'New Name'
bart.get_name() #'Bart Simpson'
3. 继承和多态
1.isinstance(dog, Animal)
在继承关系中,如果一个实例的数据类型是某个子类,那它的数据类型也可以被看做是父类。但是,反过来就不行
2. 多态
调用方只管调用,不管细节,而当我们新增一种Animal的子类时,只要确保run()方法编写正确,不用管原来的代码是如何调用的。
def run_twice(animal):
animal.run()
animal.run()
1. "开闭”原则
- 对扩展开放:允许新增Animal子类;
- 对修改封闭:不需要修改依赖Animal类型的run_twice()等函数。
class Timer(object):
def run(self):
print('Start...')
3. 静态语言 vs 动态语言
- 对于静态语言(例如Java)来说,如果需要传入Animal类型,则传入的对象必须是Animal类型或者它的子类,否则,将无法调用run()方法。
- 对于Python这样的动态语言来说,则不一定需要传入Animal类型。我们只需要保证传入的对象有一个run()方法就可以了:
class Timer(object):
def run(self):
print('Start...')
1. 鸭子类型
它并不要求严格的继承体系,一个对象只要“看起来像鸭子,走起路来像鸭子”,那它就可以被看做是鸭子。
4. 获取对象信息
1. type()
# 基本类型
type(123) #<class 'int'>
type('str') #<class 'str'>
type(None) #<type(None) 'NoneType'>
# 函数
type(abs) #<class 'builtin_function_or_method'>
# 类
type(a) #<class '__main__.Animal'>
import types
def fn():
pass
type(fn)==types.FunctionType #True
type(abs)==types.BuiltinFunctionType #True
type(lambda x: x)==types.LambdaType #True
type((x for x in range(10)))==types.GeneratorType #True
2. isinstance()
- 优先使用
- class的继承关系
- 基本类型
- 并且还可以判断一个变量是否是某些类型中的一种,比如下面的代码就可以判断是否是list或者tuple:
isinstance([1, 2, 3], (list, tuple)) #True
isinstance((1, 2, 3), (list, tuple)) #True
3.dir()
- 获得一个对象的所有属性和方法,它返回一个包含字符串的list
- 类似__ xxx__的属性和方法在Python中都是有特殊用途的,比如__ len__方法返回长度。在Python中,如果你调用len()函数试图获取一个对象的长度,实际上,在len()函数内部,它自动去调用该对象的__ len__()方法
4. getattr()、setattr()以及hasattr()
5. slots
class Student(object):
pass
s = Student()
# 给实例绑定一个属性
s.name = 'Michael'
#给实例绑定一个方法
from types import MethodType
def set_age(self, age): # 定义一个函数作为实例方法
self.age = age
s.set_age = MethodType(set_age, s)
# 但是,给一个实例绑定的方法,对另一个实例是不起作用的
s2 = Student() # 创建新的实例
s2.set_age(25) #报错
#为了给所有实例都绑定方法,可以给class绑定方法,所有实例均可调用
Student.set_age = set_age
# 通常情况下,上面的set_age 方法可以直接定义在class中
# 但动态绑定允许我们在程序运行的过程中动态给class加上功能
#这在静态语言中很难实现
#如果我们想要限制实例的属性怎么办?
class Student(object):
__slots__ = ('name', 'age') # 用tuple定义允许绑定的属性名称
#__slots__定义的属性仅对当前类实例起作用,对继承的子类是不起作用的
#除非在子类中也定义__slots__,
#这样,子类实例允许定义的属性就是自身的__slots__加上父类的__slots__。
6. @property
- 既能检查参数,又可以用类似属性这样简单的方式来访问类的变量
- 把一个getter方法变成属性,只需要加上@property就可以了,此时,@property本身又创建了另一个装饰器@score.setter,负责把一个setter方法变成属性赋值,于是,我们就拥有一个可控的属性操作:
class Student(object):
@property
def score(self):
return self._score
@score.setter
def score(self, value):
if not isinstance(value, int):
raise ValueError('score must be an integer!')
if value < 0 or value > 100:
raise ValueError('score must between 0 ~ 100!')
self._score = value
s = Student()
s.score = 60 # OK,实际转化为s.set_score(60)
s.score # 60,实际转化为s.get_score()
s.score = 9999 #报错
模块和包
模块:python中包含并有组织的代码片段,xxx.py的xxx就是模块名
包:模块之上的有层次的目录结构,定义了由很多模块或很多子包组成的应用程序执行环境。包是一个包含__init__.py文件和若干模块文件的文件夹。
库:具有相关功能模块的集合,python有很多标准库、第三方库…
猴子补丁
- 即在运行时对方法 / 类 / 属性 / 功能进行修改,把新的代码作为解决方案代替原有的程序,也就是为其打上补丁。
实例属性和类属性
- 类本身需要绑定一个属性呢?可以直接在类中定义属性,这种属性是类属性,归类所有。
- 当我们定义了一个类属性后,这个属性虽然归类所有,但类的所有实例都可以访问到。
- 在编写程序的时候,千万不要对实例属性和类属性使用相同的名字,因为相同名称的实例属性将屏蔽掉类属性,但是当你删除实例属性后,再使用相同的名称,访问到的将是类属性。
1. 用处
- 单元测试
当我们需要对处理某个函数、类的函数进行测试时,被处理的函数和类可能需要精细的构造、长时间的运行,才能出现一些罕见的行为,为此我们通过猴子补丁直接得到这些情况导致的结果,从而测试外部的函数。
2. 注
- 当进行版本更新变化的时候,很容易对补丁做出破坏
- 不知情的情况下对一个位置打两个补丁会造成替换
- 对于不知道有补丁的人来说可能会对出现的某些情况感到困惑
局部变量和全局变量
num1 = 1
def fun1():
print(num1) # 所有的函数都可以使用全局变量的值
global num1 #如果函数需要修改全局变量的值,那么需要在这个函数中
num1=2 #使用 global xxx 进行说明
print("函数内修改后num1=",num1)
global num2 = 2 #在函数内声明全局变量,加global关键字
print(globals(),locals()) #在当前作用于内显示出全局变量和局部变量的字典
def fun2():
num3 = 10
def inner(): #如果要修改嵌套作用域(enclosing 作用域,外层非全局作用域)中的变量
nonlocal num3 # nonlocal关键字声明
num3 = 100
print(num3)
inner()
print(num3)
fun2()
赋值,浅拷贝和深拷贝
1. 赋值
b = a
赋值操作(包括对象作为参数、返回值)不会开辟新的内存空间,它只是复制了对象的引用。也就是说除了 b 这个名字之外,没有其他的内存开销。
2. 浅拷贝
创建新对象,其内容非原对象本身的引用,而是原对象内第一层对象的引用
1. 形式
- 切片操作:b = a[:] 或者 b = [x for x in a];
- 工厂函数:b = list(a);
- copy 函数:b = copy.copy(a);
- 使用 is 判断可以发现他们不是同一个对象,
- 使用 id 查看,他们也不指向同一片内存空间。
- 但是当我们使用 id(x) for x in a 和 id(x) for x in b 来查看 a 和 b 中元素的地址时,可以看到二者包含的元素的地址是相同的。
- 在这种情况下,列表 a 和 b 是不同的对象,修改列表 b 理论上不会影响到列表 a。
但是要注意的是,浅拷贝之所以称之为浅拷贝,是它仅仅只拷贝了一层,在列表 a 中有一个嵌套的list,如果我们修改了它,情况就不一样了。
3. 深拷贝
1. 形式
b = copy.deepcopy(a);
- 拷贝了对象的所有元素,包括多层嵌套的元素。因此,它的时间和空间开销要高。
- 再修改列表 b 将不会影响到列表 a,即使嵌套的列表具有更深的层次,也不会产生任何影响,因为深拷贝拷贝出来的对象根本就是一个全新的对象,不再与原来的对象有任何的关联。
4. 注
- 对于非容器类型,如数字、字符,以及其他的“原子”类型,没有拷贝一说,产生的都是原对象的引用。
- 如果元组变量值包含原子类型对象,即使采用了深拷贝,也只能得到浅拷贝。
python中没有自增/自减、赋值表达式返回值和逗号表达式
- ++a、–b:看似有前置自增/自减,其实是数学运算符的取正又取正、取负又取负
- y = x = 1:正确,可以连续赋值
- y = (x = 1):错误,赋值表达式没有返回值
- python中逗号的作用:
- 分隔符:多变量同时赋值,a,b,c = 1,2,3
- 类型转换:(5)、(5, ),前为int后为tuple
错误处理和调试
1. 错误处理
try:
...
except BaseException as e:
...
except Exception as e:
...
else:
...
finally:
...
...
1.try…except…finally
- 用try…except捕获错误还有一个巨大的好处,就是可以跨越多层调用。也就是说,不需要在每个可能出错的地方去捕获错误,只要在合适的层次去捕获错误就可以了。这样一来,就大大减少了写try…except…finally的麻烦。
2. 调用栈
- 如果错误没有被捕获,它就会一直往上抛,最后被Python解释器捕获,打印一个错误信息,然后程序退出。
- 出错的时候,一定要分析错误的调用栈信息,才能定位错误的位置。
3. 记录错误
import logging
try:
...
except Exception as e:
logging.exception(e)
4. 抛出错误
- raise
- 捕获错误——记录——原样抛出(raise 不带参数)
2. 调试
1. print()
2. assert
def foo(n):
assert n != 0, 'n is zero!'
return 10 / n
- 断言失败,assert语句本身就会抛出AssertionError:
- 启动Python解释器时可以用-O参数来关闭assert。关闭后,你可以把所有的assert语句当成pass来看。
3. logging
import logging
logging.basicConfig(level=logging.INFO) #定义输出等级
s = '0'
n = int(s)
logging.info('n = %d' % n)
print(10 / n)
‘’‘
$ python err.py
INFO:root:n = 0 ##输出
Traceback (most recent call last):
File "err.py", line 8, in <module>
print(10 / n)
ZeroDivisionError: division by zero
‘’’
- 这就是logging的好处,它允许你指定记录信息的级别,有debug,info,warning,error等几个级别,当我们指定level=INFO时,logging.debug就不起作用了。同理,指定level=WARNING后,debug和info就不起作用了。这样一来,你可以放心地输出不同级别的信息,也不用删除,最后统一控制输出哪个级别的信息。
- logging的另一个好处是通过简单的配置,一条语句可以同时输出到不同的地方,比如console和文件。
4. pbd
- 启动Python解释器时可以参数-m pdb,让程序以单步方式运行,可以随时查看运行状态。
- 输入命令l来查看代码
- 输入命令n可以单步执行代码
- 任何时候都可以输入命令p 变量名来查看变量
- 输入命令q结束调试,退出程序
5. pdb.set_trace()
- 不需要单步执行,我们只需要import pdb,然后,在可能出错的地方放一个pdb.set_trace(),就可以设置一个断点
- 运行代码,程序会自动在pdb.set_trace()暂停并进入pdb调试环境,可以用命令p查看变量,或者用命令c继续运行:
参考:
https://www.liaoxuefeng.com/wiki/1016959663602400/1017075323632896
https://blog.csdn.net/zhangweijiqn/article/details/39235169
https://juejin.im/post/5ca2471df265da307b2d45a3
https://www.cnblogs.com/sfencs-hcy/p/10549898.html
https://www.nowcoder.com/discuss/393011?source_id=profile_reply&channel=1009
https://blog.csdn.net/tianmohust/article/details/7621424