Jean's Blog

一个专注软件测试开发技术的个人博客

0%

Python集合常见问题和logging日志管理模块使用

列表与*操作

python中,*操作符与list结合使用,实现元素复制

  • 复制10个|字符:

    1
    2
    >>> ['|'] * 10
    ['|', '|', '|', '|', '|', '|', '|', '|', '|', '|']
  • 复制5个空列表:

    1
    2
    >>> [[]] * 5
    [[], [], [], [], []]
  • 实例:

    1. 创建一个空列表a

      1
      >>> a = []
  1. a中的元素有一个空的list,a的长度为5,使用*复制

    1
    2
    3
    >>> a = [[]] * 5
    >>> a
    [[], [], [], [], []]
  1. 根据业务规则,如下填充元素

    1
    2
    >>> a[0].extend([1,3,5])
    >>> a[1].extend([2,4,6])
  1. 预期结果和实际结果

    • 预期结果,觉得a应该被填充为

      1
      [[1,3,5],[2,4,6],[],[]]
 - 实际结果,a填充的结果为

   
1
2
>>> a
[[1, 3, 5, 2, 4, 6], [1, 3, 5, 2, 4, 6], [1, 3, 5, 2, 4, 6], [1, 3, 5, 2, 4, 6], [1, 3, 5, 2, 4, 6]]

原来*操作复制出的a[0]a[1]、…、a[5],在内存中标识符是相等的,实现的仅仅是浅复制。

1
2
3
4
5
6
7
8
9
10
>>> a = []
>>> a = [[]] * 5
>>> a
[[], [], [], [], []]
>>> id(a[0])
4504403520
>>> id(a[1])
4504403520
>>> id(a[2])
4504403520

这种情况,就是修改其中一个元素会影响到其他的元素,那么如果要互补干扰,怎么做呢?那就要id[0]id[1]不相等。不使用*,使用列表生成式,复制出5个不同id的内嵌列表,这样就能避免复制互不干扰的问题。

1
2
3
4
5
6
7
>>> b = [[] for _ in range(5)]
>>> b
[[], [], [], [], []]
>>> b[0].extend([1,3,5])
>>> b[1].extend([2,4,6])
>>> b
[[1, 3, 5], [2, 4, 6], [], [], []]

删除列表元素

列表内元素可重复出现,如何删除列表中的某个元素。

如下方法,遍历每个元素,如果等于删除元素,使用remove删除元素。

1
2
3
4
5
6
7
8
9
10
def del_item(lst, e):
for i in lst:
if i == e:
lst.remove(i)
return lst


# 调用函数
result = del_item([1, 3, 5, 3, 2, 3], 3)
print(result)

执行结果

1
2
3
4
/Users/lvjing/PycharmProjects/python_base_project/venv/bin/python /Users/lvjing/PycharmProjects/python_base_project/demo11.py
[1, 5, 2]

Process finished with exit code 0

从结果看,这样删除方法是正确的么?例如:我们要删除列表[1, 3, 5, 3, 3, 3, 3, 2, 3]中元素3,结果[1, 5, 3, 2, 3],仍有元素3。

这是为什么呢?遍历lst、remove一次,移掉位置i后的所有元素索引都要减一。所以,一旦删除的元素,重复出现在列表中,就会漏掉一个该删除的元素。

正确做法,找到被删除元素后,进行删除,同时下次遍历索引不加一;若未找到,遍历索引加一,如下所示

1
2
3
4
5
6
7
8
9
10
11
12
13
def del_item(lst, e):
i = 0
while i < len(lst):
if lst[i] == e:
lst.remove(lst[i])
else:
i += 1
return lst


# 调用函数
result = del_item([1, 3, 5, 3, 3, 3, 3, 2, 3], 3)
print(result)

执行结果

1
2
3
4
/Users/lvjing/PycharmProjects/python_base_project/venv/bin/python /Users/lvjing/PycharmProjects/python_base_project/demo11.py
[1, 5, 2]

Process finished with exit code 0

函数默认参数为空

Python函数的参数可设置为默认值。如果一个默认参数类型为list,默认值为设置为[]。

这种默认赋值,会有问题么?

如下的代码:

1
2
3
4
5
6
7
8
9
10
11
12
def delta_val(val, volume=[]):
if volume is None:
volume = []
size = len(volume)
for i in range(size):
volume[i] = i + val
return volume


# 调用
rtn = delta_val(10)
print(rtn)

调用delta_val函数,val值为10,volume默认值,函数返回rtn为空列表。结果如下:

1
2
3
4
/Users/lvjing/PycharmProjects/python_base_project/venv/bin/python /Users/lvjing/PycharmProjects/python_base_project/demo12.py
[]

Process finished with exit code 0

下面想空列表rtn中,分别添加值1、2,打印rtn,结果符合预期

1
2
3
rtn.append(1)
rtn.append(2)
print(rtn)

执行结果:

1
2
3
4
/Users/lvjing/PycharmProjects/python_base_project/venv/bin/python /Users/lvjing/PycharmProjects/python_base_project/demo12.py
[1, 2]

Process finished with exit code 0

同样方法,再次调用 delta_val 函数,第二个参数还是取默认值。

预期返回值 rtn 应该是空列表,但是结果却出人意料!

1
2
rtn = delta_val(10)
print(rtn)

执行结果:

1
2
3
4
/Users/lvjing/PycharmProjects/python_base_project/venv/bin/python /Users/lvjing/PycharmProjects/python_base_project/demo12.py
[10, 11]

Process finished with exit code 0

为什么会返回[10, 11]呢?按照出现的结果,猜测是列表是两个元素从0进行遍历+10后,不正是[10,11],注:不太理解,IDE中Debug下一目了然。

原来调用函数delta_val时,默认参数volume取值为默认值时,并且volume作为函数的返回值。再在函数外面做一些操作,再次按照默认值调用,并返回。整个过程,默认参数volume的id始终未变。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
def delta_val(val, volume=[]):
print(id(volume))
if volume is None:
volume = []
size = len(volume)
for i in range(size):
volume[i] = i + val
return volume


# 调用
rtn = delta_val(10)
print(rtn)

rtn.append(1)
rtn.append(2)
print(rtn)

rtn = delta_val(10)
print(rtn)

执行结果

1
2
3
4
5
6
7
8
/Users/lvjing/PycharmProjects/python_base_project/venv/bin/python /Users/lvjing/PycharmProjects/python_base_project/demo12.py
4303077504
[]
[1, 2]
4303077504
[10, 11]

Process finished with exit code 0

从上面结果来看,2次调用delta_val,volume的内存标识符从未改变。

为了避免这个隐藏的坑,函数的默认参数值切记不能设置为[],而是为None。这样即便按照默认值调用多次,也会规避此风险。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
def delta_val(val, volume=None):
if volume is None:
volume = []
size = len(volume)
for i in range(size):
volume[i] = i + val
return volume


# 调用
rtn = delta_val(10)
print(rtn)

rtn.append(1)
rtn.append(2)
print(rtn)

rtn = delta_val(10)
print(rtn)

执行结果

1
2
3
4
5
6
/Users/lvjing/PycharmProjects/python_base_project/venv/bin/python /Users/lvjing/PycharmProjects/python_base_project/demo12.py
[]
[1, 2]
[]

Process finished with exit code 0

从上面来看,重复调用签名的过程,输出是符合预期的了。

{}和()

python中()是一个元组对象,例如:(1.0,3.0)

但是,初始创建的元组对象,若只有一个元素,只用一个对括号是不够的,例如下面single对象不会被解释为元组,而是float。

1
2
3
>>> single = (1.0)
>>> type(single)
<class 'float'>

要想被解释为元组,在后面必须要加一个逗号:

1
2
3
>>> single = (1.0,)
>>> type(single)
<class 'tuple'>

为什么要说这个问题呢?是因为在函数调用时,传入参数类型要求为元组。但是在传参时,若不注意丢掉了逗号,就会改变值的类型。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
def fix_points(pts):
for i in range(len(pts)):
t = pts[i]
if isinstance(t, tuple):
t = t if len(t) == 2 else (t[0], 0, 0)
pts[i] = t
else:
raise TypeError('pts的元素类型要求为元组')
return pts


# 调用
result = fix_points([(1.0, 3.0), (2.0), (5.0, 4.0)])
print(result)

执行结果

1
2
3
4
5
6
7
8
9
/Users/lvjing/PycharmProjects/python_base_project/venv/bin/python /Users/lvjing/PycharmProjects/python_base_project/demo13.py
Traceback (most recent call last):
File "/Users/lvjing/PycharmProjects/python_base_project/demo13.py", line 13, in <module>
result = fix_points([(1.0, 3.0), (2.0), (5.0, 4.0)])
File "/Users/lvjing/PycharmProjects/python_base_project/demo13.py", line 8, in fix_points
raise TypeError('pts的元素类型要求为元组')
TypeError: pts的元素类型要求为元组

Process finished with exit code 1

这样传参才是正确的

1
2
3
# 调用
result = fix_points([(1.0, 3.0), (2.0,), (5.0, 4.0)])
print(result)

执行结果

1
2
3
4
/Users/lvjing/PycharmProjects/python_base_project/venv/bin/python /Users/lvjing/PycharmProjects/python_base_project/demo13.py
[(1.0, 3.0), (2.0, 0, 0), (5.0, 4.0)]

Process finished with exit code 0

与之类似的,还有创建集合与字典,它们都用一对{},但是默认返回是字典,而不是集合。

1
2
3
4
5
6
7
8
9
>>> d = {'name':'xiaohong', 'age':'20'}
>>> s = {'xiaohong', '20'}
>>> type(d)
<class 'dict'>
>>> type(s)
<class 'set'>
>>> d = {}
>>> type(d)
<class 'dict'>

要想创建空集合,可以使用内置函数set()

1
2
3
>>> s = set()
>>> type(s)
<class 'set'>

解包

python中,支持多值赋值给多变量的操作。最常见的用法,一行代码交换两个变量:

1
2
3
4
5
6
7
>>> a, b = 1, 2
>>> a, b = b, a
>>> a
2
>>> b
1
>>>

但是,面对稍微复杂点的类似操作,如果不搞懂多赋值的执行顺讯,就会掉入陷阱。

如下例子,如果心算出的结果等于a=3,b=5,那么就说明未弄明白执行顺序。

1
2
3
4
5
6
>>> a, b = 1, 2
>>> a, b = b+1, a+b
>>> a
3
>>> b
3

从结果来看,不符合我们的预期,是因为,多值赋值是先计算出等号右侧的所有变量值后,再赋值给等号左侧变量。

这种多值赋值,是一种解包(unpack)操作。

既然是解包,那么就要先有打包。等号右侧的多个变量,会被打包(pack)为一个可迭代对象。

赋值操作,就相当于解包。这种解包操作,有时非常有用。比如,foo函数返回一个list,如下:

1
2
3
def foo():
result = [1, 'xiaoming', 'address', 'telephone', ['', '', '....']]
return result

我们现在需求只要列表的前两项。

更为简洁、紧凑的做法:等号左侧定义两个我们想要的变量,其他不想要的项放到others变量中,并在前加一个*,如下所示:

1
2
3
4
sid, name, *others = foo()
print(sid)
print(name)
print(others)

执行结果

1
2
3
4
5
6
/Users/lvjing/PycharmProjects/python_base_project/venv/bin/python /Users/lvjing/PycharmProjects/python_base_project/demo14.py
1
xiaoming
['address', 'telephone', ['', '', '....']]

Process finished with exit code 0

*others会被单独解析为一个list

访问控制

Python是一门动态语言,支持属性的动态添加和删除。而Python面向对象编程(OOP)中,提供很多双划线开头和结尾的函数,它们是系统内置方法,被称为魔法方法。如__getarr____setarr__是关于控制属性访问的方法。

重写__getattr__方法,会定义不存在属性时的行为。如下,访问类不存在属性时,程序默认会抛出AttributeError异常。

1
2
3
4
class Student:
def __init__(self, idt, name):
self.id = idt
self.name = name

上面的类,构造方法包含id和name两个属性。如果想改变以上默认行为,就可以使用__getattr__。如下,创建Student实例,调用一个不存在的address属性时,给它自动赋值None,需要注意只有某个属性不存在时,__getattr__才会被调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Student:
def __init__(self, idt, name):
self.id = idt
self.name = name

def __getattr__(self, prop_name):
print('property %s not existed, would be set to None automatically' % (prop_name,))
self.prop_name = None


xiaoming = Student(1, 'xiaoming')
# 读取不存在的属性
print(xiaoming.address)
xiaoming.address = 'beijing'
print(xiaoming.address)

调用结果

1
2
3
4
5
6
/Users/lvjing/PycharmProjects/python_base_project/venv/bin/python /Users/lvjing/PycharmProjects/python_base_project/demo15.py
property address not existed, would be set to None automatically
None
beijing

Process finished with exit code 0

从上面结果来看,直接读取Student中不存在address的属性,就会调用__getattr__,之后,我们又给address赋值,再次调用的时候,就不在调用__getattr__

还有一个关于属性赋值时行为定义的魔法方法:__setattr__而它不管属性是否存在,属性赋值前都会调用此函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Student:
def __init__(self, idt, name):
self.id = idt
self.name = name

def __getattr__(self, prop_name):
print('property %s not existed, would be set to None automatically' % (prop_name,))
self.prop_name = None

def __setattr__(self, prop_name, val):
print('%s would be set to %s' % (prop_name, str(val)))


# 调用
xiaoming = Student(1, 'xiaoming')
print(xiaoming)

执行结果

1
2
3
4
5
6
/Users/lvjing/PycharmProjects/python_base_project/venv/bin/python /Users/lvjing/PycharmProjects/python_base_project/demo15.py
id would be set to 1
name would be set to xiaoming
<__main__.Student object at 0x109ff0e20>

Process finished with exit code 0

从上面结果来看,只要涉及到属性赋值,赋值前都会调用_setattr__方法。

但是,使用它很容易掉进一个坑,__setattr__里再次设计属性赋值,这样会无线递归下去。

1
2
3
4
def __setattr__(self, prop_name, val):
print('%s would be set to %s' % (prop_name, str(val)))
# 会导致无限递归
self.prop_name = 1.0

执行结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
/Users/lvjing/PycharmProjects/python_base_project/venv/bin/python /Users/lvjing/PycharmProjects/python_base_project/demo15.py
id would be set to 1
prop_name would be set to 1.0
prop_name would be set to 1.0
prop_name would be set to 1.0
prop_name would be set to 1.0
prop_name would be set to 1.0
......
prop_name would be set to 1.0
prop_name would be set to 1.0
Traceback (most recent call last):
File "/Users/lvjing/PycharmProjects/python_base_project/demo15.py", line 17, in <module>
xiaoming = Student(1, 'xiaoming')
File "/Users/lvjing/PycharmProjects/python_base_project/demo15.py", line 3, in __init__
self.id = idt
File "/Users/lvjing/PycharmProjects/python_base_project/demo15.py", line 13, in __setattr__
self.prop_name = 1.0
File "/Users/lvjing/PycharmProjects/python_base_project/demo15.py", line 13, in __setattr__
self.prop_name = 1.0
File "/Users/lvjing/PycharmProjects/python_base_project/demo15.py", line 13, in __setattr__
self.prop_name = 1.0
[Previous line repeated 991 more times]
File "/Users/lvjing/PycharmProjects/python_base_project/demo15.py", line 11, in __setattr__
print('%s would be set to %s' % (prop_name, str(val)))
RecursionError: maximum recursion depth exceeded while calling a Python object

Process finished with exit code 1

为保险起见,不要在__setatrr__方法中再做赋值。

中括号访问

对象具有[index],返回某个元素值。那么,它们是怎么实现这种中括号索引的呢?只有重写魔法方法__getitem__,就能实现[index]功能。

如下,类Table是一个最精简的具备中括号索引的类。构造函数__init__传入一个字典,__getitem__返回字典为column_name的字典值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Table(object):
def __init__(self, df: dict):
self.df = df

def __getitem__(self, column_name):
return self.df[column_name]


# 调用
t = Table({'ids': list(range(5)), 'name': 'Li li Hua hua'.split()})
# 使用Table类,['column_name']返回对应的列
print(t['ids'])
print(t['name'])

执行结果

1
2
3
4
5
/Users/lvjing/PycharmProjects/python_base_project/venv/bin/python /Users/lvjing/PycharmProjects/python_base_project/demo16.py
[0, 1, 2, 3, 4]
['Li', 'li', 'Hua', 'hua']

Process finished with exit code 0

鸭子类型

Python是动态语言,对函数参数的类型要求很宽松,函数体内使用此类型方法或属性时,只要满足有它们就行,不强制要求必须为这个类或子类。但是,对静态类型语言,如 Java,参数类型就必须为此类型或子类。

例如,下面定义一个Plane类,定义函数using_run

1
2
3
4
5
6
7
8
9
10
11
class Plan(object):
def run(self):
print('plan is flying...')


def using_run(duck):
print(duck.run())


using_run(Plan())

执行结果

1
2
3
4
5
/Users/lvjing/PycharmProjects/python_base_project/venv/bin/python /Users/lvjing/PycharmProjects/python_base_project/duck_demo01.py
plan is flying...
None

Process finished with exit code 0

定义一个 Clock 类,它与 Plane 类没有继承关系,但是也有一个 run 方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Plan(object):
def run(self):
print('plan is flying...')


class Clock(object):
def run(self):
print('clock is rotating...')


def using_run(duck):
print(duck.run())


using_run(Clock())

执行结果

1
2
3
4
5
6
/Users/lvjing/PycharmProjects/python_base_project/venv/bin/python /Users/lvjing/PycharmProjects/python_base_project/duck_demo01.py
clock is rotating...
None

Process finished with exit code 0

Plane 对象和 Clock 对象,因都有 run 方法,Python 认为它们看起来就是 duck 类型,因此,Plane 对象和 Clock 对象就被看作 duck 类型。

元类

元类,会被 Pythoner 经常提起,元类确实也有一些使用场合。但是,它又是很高深的、偏底层的抽象类型。Python 界的领袖 Tim Peters 说过:

“元类就是深度的魔法,99% 的用户应该根本不必为此操心。”

只讲一些元类的基本知识,理解下元类是什么,怎么使用元类的一个初步理解。

xiaoming、xiaohong、xiaohua都是学生,这类群体叫做Student

Pyhton定义类的常见方案,使用关键字class:

1
2
3
>>> class Student(object):
... pass
...

xiaoming、xiaohong、xiaohua是类的实例,如下:

1
2
3
>>> xiaoming = Student()
>>> xiaohong = Student()
>>> xiaohua = Student()

创建后,xiaoming 的__class__属性,返回的便是 Student 类:

1
2
>>> xiaoming.__class__
<class '__main__.Student'>

问题在于,Student 类有 class 属性吗?如果有,返回的又是什么?

1
2
>>> xiaoming.__class__.__class__
<class 'type'>

返回 type 那么,我们不妨猜测:Student 类的类型就是 type。换句话说,Student 类就是一个对象,它的类型就是 type。因此,类也是对象。

我们都知道,程序猿的世界里都一切皆对象,会有一个更深刻的认识。

Python 中,将描述 Student 类的类被称为:元类

既然 Student 类可创建实例,那么 type 类能创建实例吗? 如果能,它创建的实例就叫:类 了。说对了,type 类一定能创建实例,如下所示,type 创建的 Student 类。

1
2
3
>>> Student = type('Student', (), {})
>>> Student
<class '__main__.Student'>

它与使用 class 关键字创建的 Student 类一模一样。

对象序列化

对象序列化,是指将内存中的对象转化为可存储或传输的过程。很多场景,直接一个类对象,传输不方便。但是,当对象序列化后,就会更加方便,因为约定俗成的,接口间的调用或者发起的 Web 请求,一般使用 JSON 串传输。

实际使用中,一般对类对象序列化。先创建一个 Student 类型,并创建两个实例。

1
2
3
4
5
6
7
8
9
class Student(object):
def __init__(self, **args):
self.ids = args['ids']
self.name = args['name']
self.address = args['address']


xiaoming = Student(ids=1, name='xiaoming', address='北京')
xiaohong = Student(ids=2, name='xiaohong', address='南京')

导入 JSON 模块,调用 dump 方法,就会将列表对象 [xiaoming,xiaohong],序列化到文件 json.txt 中。注意:json.txt当前目录创建好

1
2
3
4
import json

with open('json.txt', 'w') as f:
json.dump([xiaoming, xiaohong], f, default=lambda obj: obj.__dict__, ensure_ascii=False, indent=2, sort_keys=True)

生成的文件内容,如下:

1
2
3
4
5
6
7
8
9
10
11
12
[
{
"address": "北京",
"ids": 1,
"name": "xiaoming"
},
{
"address": "南京",
"ids": 2,
"name": "xiaohong"
}
]

日志

在调试代码,我们往往习惯使用 print 函数。通过 print,一些异常信息、变量值信息就会显示在控制台中,然后帮助我们锁定 Bug,找出问题。

但是,当项目上线后,程序一般运行在 Linux 服务器上。如果程序出现异常行为,要想通过 print 函数找出问题,可能还得安装调试代码的 IDE,在服务器上做这些事情,可能不太方便。

一般的解决方案,在代码中想 print 的信息,也要写入到日志文件中,在磁盘上保存起来。此时,遇到问题后,找到并分析对应的日志文件就行,这种解决问题的方法更可取,效率也会更高。

日志写入不是我们想象的这般简单。如果一直向同一个文件里写,文件就会变得很大很大;也不方便分析。更糟糕的是,文件越来越大,当大小等于磁盘容量时,后面的日志信息就无法再写入。当然,还有更多问题会出现。

所以,别小看写日志,我们得需要设计一套行之有效的管理体系,对日志实施有效的管理。

像大名鼎鼎的、适用于 Java 开发的 log4j,便是一套设计优秀的日志管理包。

Python 中,也有一个模块 logging,也能做到高效的日志管理。

例如,logging 模块,能按照指定周期切分日志文件。这一切的规则,都是为了实现对日志的高效管理。这些需求背后,对应着一套解决方案,也就是 logging 库,和它的四大组件:记录器处理器过滤器格式化器

下面是一个基本的日之类,同时将日志显示在控制台和写入文件中,同时按照天为周期切分日志文件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
import logging
from logging import handlers


class Logger(object):
# 日志级别关系映射
kv = {
'debug': logging.DEBUG,
'info': logging.INFO,
'warning': logging.WARNING,
'error': logging.ERROR,
'crit': logging.CRITICAL
}

def __init__(self, filename, level='info', when='D', backCount=3,
fmt='%(asctime)s - %(pathname)s[line:%(lineno)d] - %(levelname)s: %(message)s'):
self.logger = logging.getLogger(filename)
# 设置日志格式
format_str = logging.Formatter(fmt)
# 设置日志级别
self.logger.setLevel(self.kv.get(level))
# 往屏幕上输出
sh = logging.StreamHandler()
# 设置屏幕上显示的格式
sh.setFormatter(format_str)
th = handlers.TimedRotatingFileHandler(
filename=filename, when=when, backupCount=backCount, encoding='utf-8')
# 设置文件里写入的格式
th.setFormatter(format_str)
# 把对象加到 logger 里
self.logger.addHandler(sh)
self.logger.addHandler(th)

创建 log 对象,日志级别为 debug 及以上的写入日志文件:

1
log = Logger('all.log', level='debug').logger

创建 Student 类,score 属性取值只能为整型。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
from logging_demo import Logger

log = Logger('all.log', level='debug').logger


class Student:
def __init__(self, id, name):
self.id = id
self.name = name
log.info('学生 id: %s, name: %s' % (str(id), str(name)))

@property
def score(self):
return self.__score

@score.setter
def score(self, score):
if isinstance(score, int):
self.__score = score
log.info('%s得分:%d' % (self.name, self.score))
else:
log.error('学生分数类型为 %s,不是整型' % (str(type(score))))
raise TypeError('学生分数类型为 %s,不是整型' % (str(type(score))))

# 执行
xiaoming = Student(10010, 'xiaoming')
xiaoming.score = 88
xiaohong = Student(10010, 'xiaohong')
xiaohong.score = 90.6

当 score 被赋值为 90.6 时,会抛出异常,并写入错误日志到文件 all.log 中,供我们日后分析使用。

执行结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/Users/lvjing/PycharmProjects/python_base_project/venv/bin/python /Users/lvjing/PycharmProjects/python_base_project/Student.py
2023-01-30 17:23:03,074 - /Users/lvjing/PycharmProjects/python_base_project/Student.py[line:10] - INFO: 学生 id: 10010, name: xiaoming
2023-01-30 17:23:03,074 - /Users/lvjing/PycharmProjects/python_base_project/Student.py[line:20] - INFO: xiaoming得分:88
2023-01-30 17:23:03,074 - /Users/lvjing/PycharmProjects/python_base_project/Student.py[line:10] - INFO: 学生 id: 10010, name: xiaohong
2023-01-30 17:23:03,074 - /Users/lvjing/PycharmProjects/python_base_project/Student.py[line:22] - ERROR: 学生分数类型为 <class 'float'>,不是整型
Traceback (most recent call last):
File "/Users/lvjing/PycharmProjects/python_base_project/Student.py", line 30, in <module>
xiaohong.score = 90.6
File "/Users/lvjing/PycharmProjects/python_base_project/Student.py", line 23, in score
raise TypeError('学生分数类型为 %s,不是整型' % (str(type(score))))
TypeError: 学生分数类型为 <class 'float'>,不是整型

Process finished with exit code 1

查看all.log文件

1
2
3
4
5
2023-01-30 17:23:03,074 - /Users/lvjing/PycharmProjects/python_base_project/Student.py[line:10] - INFO: 学生 id: 10010, name: xiaoming
2023-01-30 17:23:03,074 - /Users/lvjing/PycharmProjects/python_base_project/Student.py[line:20] - INFO: xiaoming得分:88
2023-01-30 17:23:03,074 - /Users/lvjing/PycharmProjects/python_base_project/Student.py[line:10] - INFO: 学生 id: 10010, name: xiaohong
2023-01-30 17:23:03,074 - /Users/lvjing/PycharmProjects/python_base_project/Student.py[line:22] - ERROR: 学生分数类型为 <class 'float'>,不是整型