自学内容网 自学内容网

【测试语言篇二】Python进阶篇

目录

一、Lambda 函数

使用示例:另一个函数内的Lambda函数

使用lambda函数作为key参数的自定义排序

map 函数中使用 Lambda 函数

filter 函数中使用 Lambda 函数

reduce函数中使用Lambda函数

二、异常和错误

语法错误

异常

抛出异常

处理异常

else 语句

finally 语句

常见的内置异常

如何定义自己的异常

三、JSON

JSON格式

从Python到JSON(序列化,编码)

从JSON到Python(反序列化,解码)

使用自定义对象

编码

解码

模板编码和解码函数

四、随机数

random 模块

种子生成器

secrets 模块

NumPy的随机数

五、日志记录(logging模块)

日志级别

配置

模块内记录和记录器层次结构

传播

日志处理程序

过滤器例子

其他配置方法

.conf文件

捕获堆栈跟踪

滚动 FileHandler

TimedRotatingFileHandler

以JSON格式登录

六、星号操作符

乘法和幂运算

创建具有重复元素的列表,元组或字符串

*args, **kwargs 和仅关键字参数

拆包函数参数

拆包容器

将可迭代对象合并到列表中/合并字典


一、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)

其他配置方法

我们已经看到了如何配置日志,从而在代码中显式地创建日志记录器,处理程序和格式化程序。 还有其他两种配置方法:

.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://blog.csdn.net/m0_37135615/article/details/143570776

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!