主题
日志、配置文件、装饰器✅
一个项目,无论多么小,都应该包含日志、配置文件这两个模块。日志用于分析和优化,配置文件让程序使用更加灵活。
Python 日志
在 Python 中,可以使用内置的 logging 模块来记录日志。以下是一个简单的示例,说明如何使用 logging 模块创建并记录日志:
python
import logging
# 配置日志记录器
logging.basicConfig(filename='app.log', level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
def main():
# 记录不同级别的日志信息
logging.debug(' 这是一个调试信息 ')
logging.info(' 这是一个信息性的日志 ')
logging.warning(' 这是一个警告信息 ')
logging.error(' 这是一个错误信息 ')
logging.critical(' 这是一个严重错误信息 ')
if __name__ == "__main__":
main()在这个例子中,basicConfig() 方法用于配置日志记录器,它指定了日志文件名 (filename='app.log')、日志级别 (level=logging.INFO) 和日志记录格式 (format='%(asctime)s - %(levelname)s - %(message)s')。
然后,在 main() 函数中,通过调用不同级别的日志记录方法来记录不同类型的日志信息,例如 logging.debug()、logging.info()、logging.warning()、logging.error() 和 logging.critical()。
运行此代码后,日志信息将被记录到指定的文件 app.log 中,并根据其级别进行标识。你可以根据需要修改日志文件名、级别和格式等参数。
运行后,我们发现当前目录下多了一个 app.log ,内容如下所示:

虽然有了时间,有了日志级别,但是,还不够使用,因为我们还需要:
- 缺少打印的代码行信息,方便快速定位。
- 如果有异常发生,也需要在日志中看到异常发生。
- 我希望日志按天记录。
- 日志不仅记录到文件中,也要输出到终端。
完整代码如下:
python
import logging
from logging.handlers import TimedRotatingFileHandler
import sys
import traceback
# 日志格式配置
log_format = "%(asctime)s - %(levelname)s - %(filename)s:%(lineno)s - %(message)s"
# 配置文件处理器,每天分割一次日志
file_handler = TimedRotatingFileHandler("my_app.log", when="midnight", interval=1)
file_handler.suffix = "%Y-%m-%d"
file_handler.setFormatter(logging.Formatter(log_format))
# 配置流处理器(控制台输出)
stream_handler = logging.StreamHandler()
stream_handler.setFormatter(logging.Formatter(log_format))
# 配置日志记录器
logger = logging.getLogger()
logger.setLevel(logging.INFO)
logger.addHandler(file_handler)
logger.addHandler(stream_handler) # 添加流处理器
# 函数捕获未处理的异常
def handle_exception(exc_type, exc_value, exc_traceback):
"""
Handle uncaught exceptions.
"""
if issubclass(exc_type, KeyboardInterrupt):
sys.__excepthook__(exc_type, exc_value, exc_traceback)
return
logger.error("Uncaught exception", exc_info=(exc_type, exc_value, exc_traceback))
# 设置为默认的异常捕获方法
sys.excepthook = handle_exception
# 示例:记录一个信息日志和一个故意制造的异常
# logger.info("This is an info message.")
# raise RuntimeError("This is an example error")这样,当发生异常时,我们可以通过日志看到是哪一行代码发生了异常,从而方便查找异常原因:

假如,把上述代码保存为 log.py,这样在其他文件打印日志的时候,就可以这样来使用。
python
from log import logger
logger.info("record a log")Python 配置文件
配置文件,最常用的格式是 json 和 yaml,我比较推荐 yaml,因为可以添加注释,从而更容易理解和修改。这里我给出一个即支持 json 又支持 yaml 的代码,可以复用在任何项目中。
文件 config.py,关键部分我都做了注释,你会很容易看懂。
注意,yaml 可能还未加入到 Python 的标准库中,使用时如果报 Module Not Found,可以执行 pip install pyyaml 安装一下。
python
import json
import yaml
import os
import copy
class ConfigManager:
# 类变量,用于存储类的单例实例
_instance = None
def __new__(cls):
# 重写__new__方法以实现单例模式
if cls._instance is None:
cls._instance = super(ConfigManager, cls).__new__(cls)
cls._instance._config = {} # 初始化配置字典
return cls._instance
def load_config(self, filepath):
# 从指定文件加载配置
# 检查文件是否存在
if not os.path.exists(filepath):
raise FileNotFoundError(f"Configuration file '{filepath}' not found.")
# 获取文件扩展名
file_ext = os.path.splitext(filepath)[1]
with open(filepath, 'r', encoding="utf-8") as file:
# 根据文件类型加载配置
if file_ext == '.json':
self._config = json.load(file)
elif file_ext in ['.yml', '.yaml']:
self._config = yaml.safe_load(file)
else:
raise ValueError("Unsupported file format. Please use JSON or YAML.")
def get(self, key):
# 获取单个配置项,返回深拷贝以防外部修改
return copy.deepcopy(self._instance._config.get(key))
def get_all(self):
# 获取所有配置,返回深拷贝以防外部修改
return copy.deepcopy(self._instance._config)
# 示例用法
if __name__ == "__main__":
config_manager = ConfigManager()
# 加载并访问配置值
config_manager.load_config("config.yml")
config_data = config_manager.get_all()
print(config_data)config.py 说明:
单例模式:ConfigManager 使用单例模式,确保程序中只有一个配置管理器实例。因此,如果不需要实时获取配置信息,
config_manager.load_config("config.yml")可以仅执行一次。支持的格式:支持从JSON和YAML文件加载配置。
安全的数据访问:通过get和get_all方法提供对配置数据的访问,返回配置项的深拷贝,防止外部代码意外修改配置数据。
异常处理:如果配置文件不存在或文件格式不支持,会抛出异常。
python 装饰器
这一小节,我们先认识一下装饰器,然后分享一下我自己写的装饰器和简单用法。
1. 什么是装饰器
装饰器是 Python 中一种非常有用的特性,它允许用户在不修改原有函数代码的情况下,增加额外的功能。装饰器本质上是一个 Python 函数,它可以包裹另一个函数或方法,并可以在被包裹的函数执行前后执行一些额外的代码。
2. 基本的装饰器
让我们从一个简单的例子开始,了解装饰器是如何工作的。
2.1 创建一个基本装饰器
一个基本的装饰器通常有以下结构:
python
def my_decorator(func):
def wrapper():
print("Something is happening before the function is called.")
func()
print("Something is happening after the function is called.")
return wrapper
# 使用装饰器
@my_decorator
def say_hello():
print("Hello!")
say_hello()在这个例子中,my_decorator 是一个装饰器,它接受一个函数 func 作为参数。wrapper 是一个内部函数,它包裹了 func 的调用。当你调用 say_hello() 时,实际上是在调用 wrapper()。
2.2 输出
Something is happening before the function is called.
Hello!
Something is happening after the function is called.3. 带参数的装饰器
有时候你需要一个能够接受参数的装饰器。
3.1 创建一个带参数的装饰器
python
def decorator_with_args(arg1, arg2):
def my_decorator(func):
def wrapper(*args, **kwargs):
print(f"Arguments for decorator: {arg1}, {arg2}")
func(*args, **kwargs)
return wrapper
return my_decorator
@decorator_with_args("arg1 value", "arg2 value")
def print_args(*args):
for arg in args:
print(arg)
print_args(1, 2, 3)在这个例子中,decorator_with_args 是一个接受参数的装饰器工厂函数。它返回一个装饰器 my_decorator,该装饰器再返回实际的包装函数 wrapper。
3.2 输出
Arguments for decorator: arg1 value, arg2 value
1
2
34. 类装饰器
除了普通函数,装饰器也可以用于类。
4.1 创建一个类装饰器
python
class MyDecorator:
def __init__(self, func):
self.func = func
def __call__(self, *args, **kwargs):
print("Something is happening before the function is called.")
self.func(*args, **kwargs)
print("Something is happening after the function is called.")
@MyDecorator
def say_hello():
print("Hello!")
say_hello()类装饰器利用了类的 __call__ 特殊方法。当装饰函数时,实际上是创建了该类的一个实例,并传入被装饰的函数。
4.2 输出
Something is happening before the function is called.
Hello!
Something is happening after the function is called.装饰器是 Python 中一个强大的工具,可以在不修改原有函数定义的情况下增加额外的功能。它们可以用于日志记录、性能测试、事务处理、缓存等多种场景。通过本教程,您应该对如何创建和使用装饰器有了基本的了解。
5个非常实用的装饰器
因为自己经常用得到,就写了一个装饰器发布在了 pypi。
这里介绍一下它的用法。使用前先安装:
sh
pip install somedecorators1、 timeit
耗时统计装饰器,单位是秒,保留 4 位小数
使用方法:
python
from somedecorators import timeit
@timeit()
def test_timeit():
time.sleep(1)
#test_timeit cost 1.0026 seconds
@timeit(logger = your_logger)
def test_timeit():
time.sleep(1)2、 timeout
超时装饰器,单位是秒,函数运行超过指定的时间会抛出 TimeOutError 异常。
使用方法:
python
import time
from somedecorators import timeout
@timeout(2)
def test_timeit():
time.sleep(3)
#somedecorators.timeit.TimeoutError: Operation did not finish within 2 seconds3、 retry
重试装饰器
- 当被装饰的函数调用抛出指定的异常时,函数会被重新调用。
- 直到达到指定的最大调用次数才重新抛出指定的异常,可以指定时间间隔,默认 5 秒后重试。
- traced_exceptions 为监控的异常,可以为 None(默认)、异常类、或者一个异常类的列表或元组 tuple。
- traced_exceptions 如果为 None,则监控所有的异常;如果指定了异常类,则若函数调用抛出指定的异常时,重新调用函数,直至成功返回结果。
- 未出现监控的异常时,如果指定定了 reraised_exception 则抛出 reraised_exception,否则抛出原来的异常。
python
from somedecorators import retry
@retry(
times=2,
wait_seconds=1,
traced_exceptions=myException,
reraised_exception=CustomException,
)
def test_retry():
# time.sleep(1)
raise myException
test_retry()4、 email_on_exception
报错发邮件装饰器。当被装饰的函数调用抛出指定的异常时,函数发送邮件给指定的人员,使用独立的 djangomail 发邮件模块,非常好用。
- recipient_list: 一个字符串列表,每项都是一个邮箱地址。recipient_list 中的每个成员都可以在邮件的 "收件人:" 中看到其他的收件人。
- traced_exceptions 为监控的异常,可以为 None(默认)、异常类、或者一个异常类的元组。 traced_exceptions 如果为 None,则监控所有的异常;如果指定了异常类,则若函数调用抛出指定的异常时,发送邮件。
使用方法:
首先在项目目录新建 settings.py,配置邮件服务器或企业微信,内容如下:
python
EMAIL_USE_LOCALTIME = True
#for unitest
#EMAIL_BACKEND = 'djangomail.backends.console.EmailBackend'
#EMAIL_BACKEND = 'djangomail.backends.smtp.EmailBackend'
EMAIL_USE_SSL = True
EMAIL_HOST = 'smtp.163.com' #可以换其他邮箱
EMAIL_PORT = 465
EMAIL_HOST_USER = 'your-username'
EMAIL_HOST_PASSWORD = '********'
DEFAULT_FROM_EMAIL = EMAIL_HOST_USER
SERVER_EMAIL = EMAIL_HOST_USER
# 用于发送企业微信
CORPID="**********************" # 企业 ID
APPID="*******" # 企业应用 ID
CORPSECRET="************************" # 企业应用 Secret如果你的文件名不是 settings.py,假如是 mysettings.py 则需要修改环境变量:
python
os.environ.setdefault("SETTINGS_MODULE", "mysettings")然后主程序中这样使用:
监控所有的异常
python
from somedecorators import email_on_exception
#import os
#os.environ.setdefault("SETTINGS_MODULE", "settings") #默认配置,可以不写此行代码
@email_on_exception(['somenzz@163.com'])
def myfunc(arg):
1/arg
myfunc(0)你会收到如下的邮件信息,非常便于排查错误。
sh
Subject: myfunc(arg=0) raise Exception
From: your-username
To: somenzz@163.com
Date: Fri, 11 Jun 2021 20:55:01 -0500
Message-ID:
<162346290112.13869.15957310483971819045@1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.ip6.arpa>
myfunc(arg=0) raise Exception: division by zero
traceback:
Traceback (most recent call last):
File "/Users/aaron/github/somenzz/somedecorators/somedecorators/email.py", line 35, in wrapper
return func(*args, **kwargs)
File "/Users/aaron/github/somenzz/somedecorators/tests/tests.py", line 55, in myfunc
return 1/arg
ZeroDivisionError: division by zero
extra_msg = 严重错误监控指定的异常
python
from somedecorators import email_on_exception
import os
os.environ.setdefault("SETTINGS_MODULE", "settings")
class Exception1(Exception):
pass
class Exception2(Exception):
pass
class Exception3(Exception):
pass
@email_on_exception(['somenzz@163.com'],traced_exceptions = Exception2)
def myfunc(args):
if args == 1:
raise Exception1
elif args == 2:
raise Exception2
else:
raise Exception3
myfunc(2)上述代码只有在 raise Exception2 时才会发送邮件:
不同的异常发给不同的人
python
@email_on_exception(['somenzz@163.com'],traced_exceptions = Exception2)
@email_on_exception(['others@163.com'],traced_exceptions = (Exception1, Exception3))
def myfunc(args):
if args == 1:
raise Exception1
elif args == 2:
raise Exception2
else:
raise Exception3是不是非常方便?
5、 wechat_on_exception
异常信息发送企业微信,发送前需要在 settings.py 文件企业微信相关信息
settings.py 示例:
python
CORPID="**********************" # 企业 ID
APPID="*******" # 企业应用 ID
CORPSECRET="************************" # 企业应用 Secret调用代码
python
@wechat_on_exception(['企业微信接收者ID'],traced_exceptions = Exception2)
def myfunc(args):
if args == 1:
raise Exception1
elif args == 2:
raise Exception2
else:
raise Exception3练习题目
以下是一些关于 Python 日志、配置文件和装饰器的编程题目。请先尝试自己编写代码,然后对比答案,看看有没有不同的思路。
问题一:
题目: 编写一个 Python 程序,使用内置的 logging 模块设置日志,将日志同时输出到控制台和一个名为 log.txt 的文件中。日志级别应为 INFO。
问题二:
题目: 创建一个 Python 配置文件,包含数据库连接信息和其他配置参数。编写一个程序,读取配置文件并输出其中的配置信息。
问题三:
题目: 编写一个装饰器函数,用于测量其他函数的执行时间,并将结果输出到日志中。
问题四:
题目: 编写一个 Python 程序,使用 argparse 模块解析命令行参数,其中包括一个日志级别参数,允许用户指定日志级别。
问题五:
题目: 创建一个 Python 类,具有一个装饰器方法,该装饰器方法用于记录类方法的调用,并将日志输出到文件。
以上五个关于 Python 日志、配置文件和装饰器的编程题目的参考答案