【测试语言篇二】Python进阶篇
目录
一、Lambda 函数
Python使用lambda来创建匿名函数。
Lambda函数是一个小的(一行)、匿名的、内联函数,没有函数名称。 Lambda函数可以接受任意数量的参数,但只能具有一个表达式。
匿名函数不要使用def关键字来定义完整函数。
lambda 函数特点:
- lambda 函数是匿名的,它们没有函数名称,只能通过赋值给变量或作为参数传递给其他函数来使用。
- lambda 函数通常只包含一行代码,这使得它们适用于编写简单的函数。
lambda 语法格式: lambda arguments: expression
lambda
是 Python 的关键字,用于定义 lambda 函数。arguments
是参数列表,可以包含零个或多个参数,但必须在冒号(:
)前指定。expression
是一个表达式,用于计算并返回函数的结果。
当简单函数仅在代码中使用一次或短时间时,可以使用Lambda函数。 最常见的用途是作为高阶函数(将其他函数作为参数的函数)的参数。 它们还与诸如 map()
, filter()
, reduce()
之类的内置函数一起使用。
# 一个给参数加10的lambda函数
f = lambda x: x+10
val1 = f(5)
val2 = f(100)
print(val1, val2)
# 一个返回两个参数乘积的lambda函数
f = lambda x,y: x*y
val3 = f(2,10)
val4 = f(7,5)
print(val3, val4)
15 110 20 35
使用示例:另一个函数内的Lambda函数
从另一个函数返回定制的lambda函数,并根据需要创建不同的函数变体。
def myfunc(n):
return lambda x: x * n
doubler = myfunc(2)
print(doubler(6))
tripler = myfunc(3)
print(tripler(6))
12 18
使用lambda函数作为key参数的自定义排序
key函数会在排序之前转换每个元素。
points2D = [(1, 9), (4, 1), (5, -3), (10, 2)]
sorted_by_y = sorted(points2D, key= lambda x: x[1])
print(sorted_by_y)
mylist = [- 1, -4, -2, -3, 1, 2, 3, 4]
sorted_by_abs = sorted(mylist, key= lambda x: abs(x))
print(sorted_by_abs)
[(5, -3), (4, 1), (10, 2), (1, 9)] [-1, 1, -2, 2, -3, 3, -4, 4]
map 函数中使用 Lambda 函数
map(func, seq)
,使用函数转换每个元素。
a = [1, 2, 3, 4, 5, 6]
b = list(map(lambda x: x * 2 , a))
# 但是,尝试使用列表推导
# 如果你已经定义了函数,请使用 map
c = [x*2 for x in a]
print(b)
print(c)
numbers = [1, 2, 3, 4, 5]
squared = list(map(lambda x: x**2, numbers))
print(squared)
[2, 4, 6, 8, 10, 12] [2, 4, 6, 8, 10, 12] [1, 4, 9, 16, 25]
filter 函数中使用 Lambda 函数
filter(func, seq)
,返回其 func
计算为 True
的所有元素。
#使用 lambda 函数与 filter() 一起,筛选偶数:
a = [1, 2, 3, 4, 5, 6, 7, 8]
b = list(filter(lambda x: (x%2 == 0) , a))
# 同样可以使用列表推导实现
c = [x for x in a if x%2 == 0]
print(b)
print(c)
[2, 4, 6, 8] [2, 4, 6, 8]
reduce函数中使用Lambda函数
reduce(func, seq)
,重复将 func
应用于元素并返回单个值。func
需要2个参数。
from functools import reduce
numbers = [1, 2, 3, 4, 5]
product_a = reduce(lambda x, y: x*y, numbers)
print(product_a)
sum_a = reduce(lambda x, y: x+y, numbers)
print(sum_a)
120 15 在上面的实例中,reduce() 函数通过遍历 numbers 列表,并使用 lambda 函数将累积的结果不断更新,最终得到了 1 * 2 * 3 * 4 * 5 = 120 的结果。
二、异常和错误
Python程序在遇到错误后立即终止。在Python中,错误可以是语法错误或异常。 在本文中,我们将关注以下内容:
-
语法错误与异常
-
如何抛出异常
-
如何处理异常
-
常见的内置异常
-
如何定义自己的异常
语法错误
当解析器检测到语法不正确的语句时发生语法错误。 语法错误可以是例如拼写错误,缺少括号,没有新行(请参见下面的代码)或错误的标识(这实际上会引发它自己的IndentationError,但它是SyntaxError的子类)。
a = 5 print(a)
File "<ipython-input-5-fed4b61d14cd>", line 1 a = 5 print(a) ^ SyntaxError: invalid syntax
异常
即使一条语句在语法上是正确的,执行该语句也可能导致错误,这称为 异常错误。 有几种不同的错误类别,例如,尝试对数字和字符串求和将引发 TypeError
。
a = 5 + '10'
--------------------------------------------------------------------------- TypeError Traceback (most recent call last) <ipython-input-6-893398416ed7> in <module> ----> 1 a = 5 + '10' TypeError: unsupported operand type(s) for +: 'int' and 'str'
抛出异常
如果要在满足特定条件时强制发生异常,则可以使用 raise
关键字。
x = -5 if x < 0: raise Exception('x should not be negative.')
--------------------------------------------------------------------------- Exception Traceback (most recent call last) <ipython-input-4-2a9e7e673803> in <module> 1 x = -5 2 if x < 0: ----> 3 raise Exception('x should not be negative.') Exception: x should not be negative.
你还可以使用 assert
语句,如果你的断言不是 True
,则将引发 AssertionError
。 这样,你可以主动测试必须满足的某些条件,而不必等待程序中途崩溃。 断言还用于单元测试。
x = -5 assert (x >= 0), 'x is not positive.' # --> 如果 x >= 0,代码将正常运行
--------------------------------------------------------------------------- AssertionError Traceback (most recent call last) <ipython-input-7-f9b059c51e45> in <module> 1 x = -5 ----> 2 assert (x >= 0), 'x is not positive.' 3 # --> Your code will be fine if x >= 0 AssertionError: x is not positive.
处理异常
你可以使用 try
和 except
块来捕获和处理异常。 如果你可以捕获异常,则你的程序将不会终止,并且可以继续。
# 这将捕获所有可能的异常
try:
a = 5 / 0
except:
print('some error occured.')
# 可以捕获异常类型
try:
a = 5 / 0
except Exception as e:
print(e)
# 最好指定要捕获的异常类型
# 因此,你必须知道可能的错误
try:
a = 5 / 0
except ZeroDivisionError:
print('Only a ZeroDivisionError is handled here')
# 你可以在try块中运行多个语句,并捕获不同的可能的异常
try:
a = 5 / 1 # 注意:这里没有 ZeroDivisionError
b = a + '10'
except ZeroDivisionError as e:
print('A ZeroDivisionError occured:', e)
except TypeError as e:
print('A TypeError occured:', e)
Some error occured. Division by zero Only a ZeroDivisionError is handled here A TypeError occured: unsupported operand type(s) for +: 'float' and 'str'
else
语句
如果没有发生异常,则可以使用else语句运行。
try:
a = 5 / 1
except ZeroDivisionError as e:
print('A ZeroDivisionError occured:', e)
else:
print('Everything is ok')
Everything is ok
finally
语句
你可以使用始终运行的 finally
语句,无论是否存在异常。 例如,这可用于进行一些清理操作。
try:
a = 5 / 1 # 注意:这里没有 ZeroDivisionError
b = a + '10'
except ZeroDivisionError as e:
print('A ZeroDivisionError occured:', e)
except TypeError as e:
print('A TypeError occured:', e)
else:
print('Everything is ok')
finally:
print('Cleaning up some stuff...')
A TypeError occured: unsupported operand type(s) for +: 'float' and 'str' Cleaning up some stuff...
常见的内置异常
你可以在此处找到所有内置的异常:https://docs.python.org/3/library/exceptions.html
-
ImportError
:如果无法导入模块 -
NameError
:如果你尝试使用未定义的变量 -
FileNotFoundError
:如果你尝试打开一个不存在的文件或指定了错误的路径 -
ValueError
:当某个操作或函数收到类型正确但值不正确的参数时,例如尝试从不存在的列表中删除值 -
TypeError
:将操作或函数应用于不适当类型的对象时引发。 -
IndexError
:如果你尝试访问序列的无效索引,例如列表或元组。 -
KeyError
:如果你尝试访问字典中不存在的键。
# ImportError
import nonexistingmodule
# NameError
a = someundefinedvariable
# FileNotFoundError
with open('nonexistingfile.txt') as f:
read_data = f.read()
# ValueError
a = [0, 1, 2]
a.remove(3)
# TypeError
a = 5 + "10"
# IndexError
a = [0, 1, 2]
value = a[5]
# KeyError
my_dict = {"name": "Max", "city": "Boston"}
age = my_dict["age"]
如何定义自己的异常
你可以定义自己的异常类,该异常类应从内置的 Exception
类派生。 与标准异常的命名类似,大多数异常都以“错误”结尾的名称定义。 可以像定义其他任何类一样定义异常类,但是它们通常保持简单,通常仅提供一定数量的属性,这些属性允许处理程序提取有关错误的信息。
# 自定义异常类的最小示例
class ValueTooHighError(Exception):
pass
# 或者为处理者添加一些信息
class ValueTooLowError(Exception):
def __init__(self, message, value):
self.message = message
self.value = value
def test_value(a):
if a > 1000:
raise ValueTooHighError('Value is too high.')
if a < 5:
raise ValueTooLowError('Value is too low.', a) # 注意,构造器接受两个参数
return a
try:
test_value(1)
except ValueTooHighError as e:
print(e)
except ValueTooLowError as e:
print(e.message, 'The value is:', e.value)
Value is too low. The value is: 1
三、JSON
JSON(JavaScript对象表示法)是一种轻量级数据格式,用于数据交换。 在Python中具有用于编码和解码JSON数据的内置 json
模块。 只需导入它,就可以使用JSON数据了:
import json
JSON的一些优点:
-
JSON作为“字节序列”存在,在我们需要通过网络传输(流)数据的情况下非常有用。
-
与XML相比,JSON小得多,可转化为更快的数据传输和更好的体验。
-
JSON非常文本友好,因为它是文本形式的,并且同时也是机器友好的。
JSON格式
{ "firstName": "Jane", "lastName": "Doe", "hobbies": ["running", "swimming", "singing"], "age": 28, "children": [ { "firstName": "Alex", "age": 5 }, { "firstName": "Bob", "age": 7 } ] }
JSON支持基本类型(字符串,数字,布尔值)以及嵌套的数组和对象。 根据以下转换,将简单的Python对象转换为JSON:
|Python|JSON| |—|—| |dict|object| |”list, tuple”|array| |str|string| |”int, long, float”|number| |True|true| |False|false| |None|null|
从Python到JSON(序列化,编码)
使用 json.dumps()
方法将Python对象转换为JSON字符串。
import json
person = {"name": "John", "age": 30, "city": "New York", "hasChildren": False, "titles": ["engineer", "programmer"]}
# 转为 JSON:
person_json = json.dumps(person)
# 使用不用的格式
person_json2 = json.dumps(person, indent=4, separators=("; ", "= "), sort_keys=True)
# 结果为 JSON 字符串
print(person_json)
print(person_json2)
{"name": "John", "age": 30, "city": "New York", "hasChildren": false, "titles":["engineer", "programmer"]} { "age"= 30; "city"= "New York"; "hasChildren"= false; "name"= "John"; "titles"= [ "engineer"; "programmer" ] }
或将Python对象转换为JSON对象,然后使用 json.dump()
方法将其保存到文件中。
import json
person = {"name": "John", "age": 30, "city": "New York", "hasChildren": False, "titles": ["engineer", "programmer"]}
with open('person.json', 'w') as f:
json.dump(person, f) # 你也可以设置缩进等
从JSON到Python(反序列化,解码)
使用 json.loads()
方法将JSON字符串转换为Python对象。 结果将是一个Python字典。
import json
person_json = """
{
"age": 30,
"city": "New York",
"hasChildren": false,
"name": "John",
"titles": [
"engineer",
"programmer"
]
}
"""
person = json.loads(person_json)
print(person)
{'age': 30, 'city': 'New York', 'hasChildren': False, 'name': 'John', 'titles': ['engineer', 'programmer']}
或从文件加载数据,然后使用 json.load()方法将其转换为Python对象。
import json
with open('person.json', 'r') as f:
person = json.load(f)
print(person)
{'name': 'John', 'age': 30, 'city': 'New York', 'hasChildren': False, 'titles': ['engineer', 'programmer']}
使用自定义对象
编码
使用默认的 JSONEncoder
编码自定义对象将引发 TypeError
。 我们可以指定一个自定义的编码函数,该函数将类名和所有对象变量存储在字典中。 将此函数用作 json.dump()
方法中的 default
参数。
import json
def encode_complex(z):
if isinstance(z, complex):
# 只是类名的键很重要,值可以是任意的。
return {z.__class__.__name__: True, "real":z.real, "imag":z.imag}
else:
raise TypeError(f"Object of type '{z.__class__.__name__}' is not JSON serializable")
z = 5 + 9j
zJSON = json.dumps(z, default=encode_complex)
print(zJSON)
{"complex": true, "real": 5.0, "imag": 9.0}
你还可以创建一个自定义的 Encoder 类,并覆盖 default()
方法。 将其用于 json.dump()
方法中的 cls
参数,或直接使用编码器。
from json import JSONEncoder
class ComplexEncoder(JSONEncoder):
def default(self, o):
if isinstance(z, complex):
return {z.__class__.__name__: True, "real":z.real, "imag":z.imag}
# 让基类的默认方法处理其他对象或引发TypeError
return JSONEncoder.default(self, o)
z = 5 + 9j
zJSON = json.dumps(z, cls=ComplexEncoder)
print(zJSON)
# 或者直接使用编码器
zJson = ComplexEncoder().encode(z)
print(zJSON)
{"complex": true, "real": 5.0, "imag": 9.0} {"complex": true, "real": 5.0, "imag": 9.0}
解码
可以使用默认 JSONDecoder 解码自定义对象,但是它将被解码为字典。 编写一个自定义解码函数,该函数将以字典作为输入,并在可以在字典中找到对象类名称的情况下创建自定义对象。 将此函数用于 json.load()
方法中的 object_hook
参数。
# 可能但解码为字典
z = json.loads(zJSON)
print(type(z))
print(z)
def decode_complex(dct):
if complex.__name__ in dct:
return complex(dct["real"], dct["imag"])
return dct
# 现在,对象在解码后的类型为complex
z = json.loads(zJSON, object_hook=decode_complex)
print(type(z))
print(z)
<class 'dict'> {'complex': True, 'real': 5.0, 'imag': 9.0} <class 'complex'> (5+9j)
模板编码和解码函数
如果在 __init__
方法中提供了所有类变量,则此方法适用于所有自定义类。
class User:
# 自定义类在 __init__() 中包含所有类变量
def __init__(self, name, age, active, balance, friends):
self.name = name
self.age = age
self.active = active
self.balance = balance
self.friends = friends
class Player:
# 其他自定义类
def __init__(self, name, nickname, level):
self.name = name
self.nickname = nickname
self.level = level
def encode_obj(obj):
"""
接受一个自定义对象,并返回该对象的字典表示形式。 此字典表示形式还包括对象的模块和类名称。
"""
# 用对象元数据填充字典
obj_dict = {
"__class__": obj.__class__.__name__,
"__module__": obj.__module__
}
# 用对象属性填充字典
obj_dict.update(obj.__dict__)
return obj_dict
def decode_dct(dct):
"""
接受字典并返回与该字典关联的自定义对象。
它利用字典中的 "__module__" 和 "__class__" 元数据来了解要创建的对象类型。
"""
if "__class__" in dct:
# Pop ensures we remove metadata from the dict to leave only the instance arguments
class_name = dct.pop("__class__")
# Get the module name from the dict and import it
module_name = dct.pop("__module__")
# We use the built in __import__ function since the module name is not yet known at runtime
module = __import__(module_name)
# Get the class from the module
class_ = getattr(module,class_name)
# Use dictionary unpacking to initialize the object
# Note: This only works if all __init__() arguments of the class are exactly the dict keys
obj = class_(**dct)
else:
obj = dct
return obj
# User 类适用于我们的编码和解码方法
user = User(name = "John",age = 28, friends = ["Jane", "Tom"], balance = 20.70, active = True)
userJSON = json.dumps(user,default=encode_obj,indent=4, sort_keys=True)
print(userJSON)
user_decoded = json.loads(userJSON, object_hook=decode_dct)
print(type(user_decoded))
# Player 类也适用于我们的编码和解码方法
player = Player('Max', 'max1234', 5)
playerJSON = json.dumps(player,default=encode_obj,indent=4, sort_keys=True)
print(playerJSON)
player_decoded = json.loads(playerJSON, object_hook=decode_dct)
print(type(player_decoded))
{ "__class__": "User", "__module__": "__main__", "active": true, "age": 28, "balance": 20.7, "friends": [ "Jane", "Tom" ], "name": "John" } <class '__main__.User'> { "__class__": "Player", "__module__": "__main__", "level": 5, "name": "Max", "nickname": "max1234" } <class '__main__.Player'>
四、随机数
Python定义了一组用于生成或操作随机数的函数。 本文介绍:
-
random
模块 -
用
random.seed()
再生产数字 -
使用
secrets
模块创建密码学上强的随机数 -
用
numpy.random
创建随机 nd 数组
random
模块
该模块为各种版本实现伪随机数生成器。它使用Mersenne Twister算法(https://en.wikipedia.org/wiki/Mersenne_Twister)作为其核心生成器。 之所以称其为伪随机数,是因为数字看起来是随机的,但是是可重现的。
import random
# [0,1) 之间随机浮点数
a = random.random()
print(a)
# [a,b] 之间随机浮点数
a = random.uniform(1,10)
print(a)
# [a,b] 之间随机整数,b 包括。
a = random.randint(1,10)
print(a)
# 之间随机整数,b 不包括。
a = random.randrange(1,10)
print(a)
# 参数为 mu 和 sigma 的正态分布随机浮点数
a = random.normalvariate(0, 1)
print(a)
# 从序列中随机选择元素
a = random.choice(list("ABCDEFGHI"))
print(a)
# 从序列中随机选择 k 个唯一元素
a = random.sample(list("ABCDEFGHI"), 3)
print(a)
# 选择可重复的k个元素,并返回大小为k的列表
a = random.choices(list("ABCDEFGHI"),k=3)
print(a)
# 原地随机排列
a = list("ABCDEFGHI")
random.shuffle(a)
print(a)
0.10426373452067317 3.34983979352444 3 4 -1.004568769635799 E ['G', 'C', 'B'] ['E', 'D', 'E'] ['D', 'I', 'G', 'H', 'E', 'B', 'C', 'F', 'A']
种子生成器
使用 random.seed()
,可以使结果可重复,并且 random.seed()
之后的调用链将产生相同的数据轨迹。 随机数序列变得确定,或完全由种子值确定。
print('Seeding with 1...\n')
random.seed(1)
print(random.random())
print(random.uniform(1,10))
print(random.choice(list("ABCDEFGHI")))
print('\nRe-seeding with 42...\n')
random.seed(42) # 重设随机种子
print(random.random())
print(random.uniform(1,10))
print(random.choice(list("ABCDEFGHI")))
print('\nRe-seeding with 1...\n')
random.seed(1) # 重设随机种子
print(random.random())
print(random.uniform(1,10))
print(random.choice(list("ABCDEFGHI")))
print('\nRe-seeding with 42...\n')
random.seed(42) # 重设随机种子
print(random.random())
print(random.uniform(1,10))
print(random.choice(list("ABCDEFGHI")))
Seeding with 1... 0.13436424411240122 8.626903632435095 B Re-seeding with 42... 0.6394267984578837 1.2250967970040025 E Re-seeding with 1... 0.13436424411240122 8.626903632435095 B Re-seeding with 42... 0.6394267984578837 1.2250967970040025 E
secrets
模块
secrets
模块用于生成适合于管理数据(例如密码,帐户身份验证,安全令牌和相关机密)的密码学上强的随机数。
特别是,应优先使用secrets
而不是 random
模块中默认的伪随机数生成器,后者是为建模和仿真而设计的,而不是安全或加密技术。
import secrets
# [0, n) 之间的随机整数。
a = secrets.randbelow(10)
print(a)
# 返回具有k个随机位的整数。
a = secrets.randbits(5)
print(a)
# 从序列中选择一个随机元素
a = secrets.choice(list("ABCDEFGHI"))
print(a)
6 6 E
NumPy的随机数
为多维数组创建随机数。NumPy伪随机数生成器与Python标准库伪随机数生成器不同。
重要的是,设置Python伪随机数生成器种子不会影响NumPy伪随机数生成器,必须单独设置和使用。
import numpy as np
np.random.seed(1)
# rand(d0,d1,…,dn)
# 生成随机浮点数的多维数组, 数组大小为 (d0,d1,…,dn)
print(np.random.rand(3))
# 重设随机种子
np.random.seed(1)
print(np.random.rand(3))
# 生成 [a,b) 之间随机整数的多维数组,大小为 n
values = np.random.randint(0, 10, (5,3))
print(values)
# 使用正态分布值生成多维数组,数组大小为 (d0,d1,…,dn)
# 来自标准正态分布的平均值为0.0且标准偏差为1.0的值
values = np.random.randn(5)
print(values)
# 随机排列一个多维数组.
# 仅沿多维数组的第一轴随机排列数组
arr = np.array([[1,2,3], [4,5,6], [7,8,9]])
np.random.shuffle(arr)
print(arr)
[4.17022005e-01 7.20324493e-01 1.14374817e-04] [4.17022005e-01 7.20324493e-01 1.14374817e-04] [[5 0 0] [1 7 6] [9 2 4] [5 2 4] [2 4 7]] [-2.29230928 -1.41555249 0.8858294 0.63190187 0.04026035] [[4 5 6] [7 8 9] [1 2 3]]
五、日志记录(logging模块)
Python中的日志记录模块是功能强大的内置模块,因此你可以快速将日志记录添加到应用程序中。
import logging
日志级别
有5种不同的日志级别指示事件的严重程度。 默认情况下,系统仅记录 警告(WARNING) 级别及更高级别的事件。
import logging
logging.debug('This is a debug message')
logging.info('This is an info message')
logging.warning('This is a warning message')
logging.error('This is an error message')
logging.critical('This is a critical message')
WARNING:root:This is a warning message ERROR:root:This is an error message CRITICAL:root:This is a critical message
配置
使用 basicConfig(**kwargs)
,你可以自定义根记录器。 最常见的参数是 level
, format
和 filename
。查看全部可能的参数:logging — Logging facility for Python — Python 3.13.0 documentation。查看可能的 format :logging — Logging facility for Python — Python 3.13.0 documentation。查看如何设置时间字符串:time — Time access and conversions — Python 3.13.0 documentation。请注意,此函数仅应调用一次,通常在导入模块后首先调用。 如果根记录器已经配置了处理程序,则该设置无效。 例如,在 basicConfig
之前调用 logging.info(...)
将提前设置处理程序。
import logging logging.basicConfig(level=logging.DEBUG, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', datefmt='%m/%d/%Y %H:%M:%S') # 现在,调试消息也将以其他格式记录。 logging.debug('Debug message') # 这将记录到文件而不是控制台。 # logging.basicConfig(level=logging.DEBUG, filename='app.log')
模块内记录和记录器层次结构
在具有多个模块的应用程序中,最佳实践是使用 __name__
全局变量创建内部记录器。 这将使用你的模块名称创建一个记录器,并确保没有名称冲突。 日志记录模块创建记录器的层次结构,从根记录器开始,然后将新的记录器添加到该层次结构中。 如果随后将模块导入另一个模块,则可以通过记录器名称将日志消息与正确的模块关联。 请注意,更改根记录器的 basicConfig
还将影响层次结构中其他(下部)记录器的日志事件。
# helper.py
# -------------------------------------
import logging
logger = logging.getLogger(__name__)
logger.info('HELLO')
# main.py
# -------------------------------------
import logging
logging.basicConfig(level=logging.INFO, format='%(name)s - %(levelname)s - %(message)s')
import helper
# --> 当运行 main.py 时的输出
# helper - INFO - HELLO
传播
默认情况下,除了附加到创建的记录器的任何处理程序外,所有创建的记录器还将日志事件传递给高级记录器的处理程序。 你可以通过设置 propagate = False
来禁用此功能。 有时,当你想知道为什么看不到来自另一个模块的日志消息时,则可能是此属性。
# ------------------------------------- import logging logger = logging.getLogger(__name__) logger.propagate = False logger.info('HELLO') # main.py # ------------------------------------- import logging logging.basicConfig(level=logging.INFO, format='%(name)s - %(levelname)s - %(message)s') import helper # --> 运行main.py时无输出,因为 helper 模块记录器不会将其消息传播到根记录器
日志处理程序
处理程序对象负责将适当的日志消息调度到处理程序的特定目标。 例如,你可以使用不同的处理程序通过HTTP或通过电子邮件将消息发送到标准输出流,文件。 通常,你为每个处理程序配置一个级别( setLevel()
),一个格式化程序( setFormatter()
)和一个可选的过滤器( addFilter()
)。 有关可能的内置处理程序,请参见 Logging HOWTO — Python 3.13.0 documentation。 当然,你也可以通过派生这些类来实现自己的处理程序。
import logging
logger = logging.getLogger(__name__)
# 创建处理器
stream_handler = logging.StreamHandler()
file_handler = logging.FileHandler('file.log')
# 配置级别和格式化程序,并添加到处理器上
stream_handler.setLevel(logging.WARNING) # 警告及以上级别日志记录到流中
file_handler.setLevel(logging.ERROR) # 错误及以上级别记录到文件中
stream_format = logging.Formatter('%(name)s - %(levelname)s - %(message)s')
file_format = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
stream_handler.setFormatter(stream_format)
file_handler.setFormatter(file_format)
# 添加处理器到日志记录器上
logger.addHandler(stream_handler)
logger.addHandler(file_handler)
logger.warning('This is a warning') # 记录到流中
logger.error('This is an error') # 记录到流和文件中
过滤器例子
class InfoFilter(logging.Filter): # 覆盖此方法。 仅此方评估为True的日志记录将通过过滤器。 def filter(self, record): return record.levelno == logging.INFO # 现在只有 INFO 级别的消息会被记录。 stream_handler.addFilter(InfoFilter()) logger.addHandler(stream_handler)
其他配置方法
我们已经看到了如何配置日志,从而在代码中显式地创建日志记录器,处理程序和格式化程序。 还有其他两种配置方法:
-
创建日志记录配置文件并使用
fileConfig()
函数读取它。 请参见下面的示例。 -
创建配置信息字典并将其传递给
dictConfig()
函数。 有关更多信息,请参见logging.config — Logging configuration — Python 3.13.0 documentation。
.conf文件
创建一个 .conf(或有时存储为 .ini)文件,定义记录器,处理程序和格式化程序,并提供名称作为键。 定义其名称后,可以通过在其名称之间用下划线分隔之前添加单词 logger, handler 和 formatter 进行配置。 然后,你可以为每个记录器,处理程序和格式化程序设置属性。 在下面的示例中,将使用 StreamHandler 配置根记录器和名为 simpleExample 的记录器。
# logging.conf [loggers] keys=root,simpleExample [handlers] keys=consoleHandler [formatters] keys=simpleFormatter [logger_root] level=DEBUG handlers=consoleHandler [logger_simpleExample] level=DEBUG handlers=consoleHandler qualname=simpleExample propagate=0 [handler_consoleHandler] class=StreamHandler level=DEBUG formatter=simpleFormatter args=(sys.stdout,) [formatter_simpleFormatter] format=%(asctime)s - %(name)s - %(levelname)s - %(message)s
# 在代码中使用配置文件 import logging import logging.config logging.config.fileConfig('logging.conf') # 使用配置文件中的名称创建记录器。 # 该记录器现在具有带有 DEBUG 级别和指定格式的 StreamHandler logger = logging.getLogger('simpleExample') logger.debug('debug message') logger.info('info message')
捕获堆栈跟踪
将跟踪记录记录在异常日志中对于解决问题非常有用。 你可以通过将 excinfo 参数设置为True来捕获 logging.error()
中的回溯。
import logging try: a = [1, 2, 3] value = a[3] except IndexError as e: logging.error(e) logging.error(e, exc_info=True)
ERROR:root:list index out of range ERROR:root:list index out of range Traceback (most recent call last): File "<ipython-input-6-df97a133cbe6>", line 5, in <module> value = a[3] IndexError: list index out of range
如果未捕获正确的 Exception,则还可以使用 traceback.formatexc() 方法记录该异常。
滚动 FileHandler
当你有一个大型应用程序将许多事件记录到一个文件中,而你只需要跟踪最近的事件时,请使用RotatingFileHandler来使文件保持较小。 当日志达到一定数量的字节时,它将被“滚动”。 你还可以保留多个备份日志文件,然后再覆盖它们。
import logging
from logging.handlers import RotatingFileHandler
logger = logging.getLogger(__name__)
logger.setLevel(logging.INFO)
# 2KB后滚动,并保留备份日志为 app.log.1, app.log.2 等.
handler = RotatingFileHandler('app.log', maxBytes=2000, backupCount=5)
logger.addHandler(handler)
for _ in range(10000):
logger.info('Hello, world!')
TimedRotatingFileHandler
如果你的应用程序将长时间运行,则可以使用 TimedRotatingFileHandler。 这将根据经过的时间创建一个轮换日志。 when 参数的可能时间条件是:
-
second (s)
-
minute (m)
-
hour (h)
-
day (d)
-
w0-w6 (工作日, 0=星期一)
-
midnight
import logging
import time
from logging.handlers import TimedRotatingFileHandler
logger = logging.getLogger(__name__)
logger.setLevel(logging.INFO)
# 这将每分钟创建一个新的日志文件,并在覆盖旧日志之前创建一个带有时间戳的5个备份文件。
handler = TimedRotatingFileHandler('timed_test.log', when='m', interval=1, backupCount=5)
logger.addHandler(handler)
for i in range(6):
logger.info('Hello, world!')
time.sleep(50)
以JSON格式登录
如果你的应用程序从不同的模块(特别是在微服务体系结构中)生成许多日志,那么定位重要的日志以进行分析可能会很困难。 因此,最佳实践是以JSON格式记录你的消息,并将其发送到集中式日志管理系统。 然后,你可以轻松地搜索,可视化和分析日志记录。
我建议使用此开源JSON记录器:https://github.com/madzak/python-json-logger
pip install python-json-logger
import logging
from pythonjsonlogger import jsonlogger
logger = logging.getLogger()
logHandler = logging.StreamHandler()
formatter = jsonlogger.JsonFormatter()
logHandler.setFormatter(formatter)
logger.addHandler(logHandler)
六、星号操作符
星号( *
)可用于Python中的不同情况:
-
乘法和幂运算
-
创建具有重复元素的列表,元组或字符串
-
*args
,**kwargs
和仅关键字参数 -
拆包列表/元组/字典的函数参数
-
拆包容器
-
将可迭代对象合并到列表中/合并字典
乘法和幂运算
# 乘法
result = 7 * 5
print(result)
# 幂运算
result = 2 ** 4
print(result)
35 16
创建具有重复元素的列表,元组或字符串
# list
zeros = [0] * 10
onetwos = [1, 2] * 5
print(zeros)
print(onetwos)
# tuple
zeros = (0,) * 10
onetwos = (1, 2) * 5
print(zeros)
print(onetwos)
# string
A_string = "A" * 10
AB_string = "AB" * 5
print(A_string)
print(AB_string)
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0] [1, 2, 1, 2, 1, 2, 1, 2, 1, 2] (0, 0, 0, 0, 0, 0, 0, 0, 0, 0) (1, 2, 1, 2, 1, 2, 1, 2, 1, 2) AAAAAAAAAA ABABABABAB
*args
, **kwargs
和仅关键字参数
-
对可变长度参数使用
*args
-
对长度可变的关键字参数使用
**kwargs
-
使用
*
,后跟更多函数参数以强制使用仅关键字的参数
def my_function(*args, **kwargs):
for arg in args:
print(arg)
for key in kwargs:
print(key, kwargs[key])
my_function("Hey", 3, [0, 1, 2], name="Alex", age=8)
# '*' 或 '* identifier' 之后的参数是仅关键字参数,只能使用关键字参数传递。
def my_function2(name, *, age):
print(name)
print(age)
# my_function2("Michael", 5) --> 这会引发 TypeError 错误
my_function2("Michael", age=5)
Hey 3 [0, 1, 2] name Alex age 8 Michael 5
拆包函数参数
-
如果长度与参数匹配,则列表/元组/集合/字符串可以用
*
拆成函数参数。 -
如果长度和键与参数匹配,则字典可以用两个
**
拆包。
def foo(a, b, c):
print(a, b, c)
# 长度必需匹配
my_list = [1, 2, 3]
foo(*my_list)
my_string = "ABC"
foo(*my_string)
# 长度和键必需匹配
my_dict = {'a': 4, 'b': 5, 'c': 6}
foo(**my_dict)
1 2 3 A B C 4 5 6
拆包容器
将列表,元组或集合的元素拆包为单个和多个剩余元素。 请注意,即使被拆包的容器是元组或集合,也将多个元素组合在一个列表中。
numbers = (1, 2, 3, 4, 5, 6, 7, 8)
*beginning, last = numbers
print(beginning)
print(last)
print()
first, *end = numbers
print(first)
print(end)
print()
first, *middle, last = numbers
print(first)
print(middle)
print(last)
[1, 2, 3, 4, 5, 6, 7] 8 1 [2, 3, 4, 5, 6, 7, 8] 1 [2, 3, 4, 5, 6, 7] 8
将可迭代对象合并到列表中/合并字典
由于PEP 448(PEP 448 – Additional Unpacking Generalizations | peps.python.org),从Python 3.5开始,这是可能的。
# 将可迭代对象合并到列表中
my_tuple = (1, 2, 3)
my_set = {4, 5, 6}
my_list = [*my_tuple, *my_set]
print(my_list)
# 用字典拆包合并两个字典
dict_a = {'one': 1, 'two': 2}
dict_b = {'three': 3, 'four': 4}
dict_c = {**dict_a, **dict_b}
print(dict_c)
[1, 2, 3, 4, 5, 6] {'one': 1, 'two': 2, 'three': 3, 'four': 4}
但是,请注意以下合并解决方案。 如果字典中有任何非字符串键,则它将不起作用:https://stackoverflow.com/questions/38987/how-to-merge-two-dictionaries-in-a-single-expression/39858#39858
dict_a = {'one': 1, 'two': 2}
dict_b = {3: 3, 'four': 4}
dict_c = dict(dict_a, **dict_b)
print(dict_c)
# 以下可行:
# dict_c = {**dict_a, **dict_b}
--------------------------------------------------------------------------- TypeError Traceback (most recent call last) <ipython-input-52-2660fb90a60f> in <module> 1 dict_a = {'one': 1, 'two': 2} 2 dict_b = {3: 3, 'four': 4} ----> 3 dict_c = dict(dict_a, **dict_b) 4 print(dict_c) 5 TypeError: keywords must be strings
推荐进一步阅读:
-
https://treyhunner.com/2018/10/asterisks-in-python-what-they-are-and-how-to-use-them/
-
https://treyhunner.com/2016/02/how-to-merge-dictionaries-in-python/
原文地址:https://blog.csdn.net/m0_37135615/article/details/143570776
免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!