目录
一、Python元编程装饰器介绍
二、装饰器使用
1. 实现认证和授权功能
2.实现缓存功能
3.实现日志输出功能
三、附录
1. logging.basicConfig介绍
2. 精确到毫秒,打印时间
方法一:使用datetime
方法二:使用time
一、Python元编程装饰器介绍
Python元编程是指在Python中使用语言自身来创建、修改、或操纵其它代码的能力。其中,装饰器是元编程中最常见的一种技术。
装饰器是一种函数,其接受一个函数作为参数,并返回另一个函数。这通常被用于修改或扩展现有函数的功能。
装饰器在Python中的优点包括:
- 使代码更加模块化:通过装饰器,我们可以将不同的功能逻辑分离到不同的装饰器中,使代码更加模块化。
- 简化代码:使用装饰器可以避免代码重复,使代码更加简洁明了。
- 提高代码的可重用性:由于装饰器本身就是一种可重用的代码模式,所以可以提高代码的可重用性。
装饰器在Python中的缺点包括:
- 可读性较差:有时装饰器会让代码变得更加难以理解和调试。
- 装饰器的嵌套:如果使用多个装饰器来实现某个功能,可能会导致装饰器的嵌套过于复杂。
装饰器在应用程序开发中的应用场景包括:
- 认证和授权:通过装饰器,可以在需要进行认证和授权的函数前添加一个用于检查权限的装饰器。
- 缓存:使用装饰器可以轻松地添加缓存逻辑,避免重复计算。
- 日志:使用装饰器可以很方便地处理日志输出。
装饰器的使用方式如下:
def my_decorator(func):
def wrapper(*args, **kwargs):
# before
print("before...")
result = func(*args, **kwargs)
# after
print("after...")
return result
# 返回包装后的函数
return wrapper
@my_decorator
def say_test():
print("test")
say_test()
运行结果:
before...
test
after...
二、装饰器使用
1. 实现认证和授权功能
下面以一个简单的例子来说明如何使用Python元编程装饰器实现认证和授权功能。
假设我们有一个网络应用程序,其中包含一些需要身份验证的受保护页面。我们希望使用装饰器来实现身份验证和授权功能。
首先,我们可以定义一个装饰器函数authenticate
,用于验证用户身份:
在这个装饰器函数中,我们将原始函数func
封装成一个新的函数wrapper
。在wrapper
函数中,我们首先检查当前用户是否已经通过身份验证,如果是,则调用原始函数func
并将它的参数传递给它,否则抛出一个ValueError
异常。
接下来,我们定义一个装饰器函数authorize
,用于授权用户访问某个页面:
在这个装饰器函数中,我们首先接受一个roles
列表,用于指定允许访问该页面的用户角色。然后,我们返回一个新的装饰器函数decorator
,它接受原始函数func
作为参数,并返回一个新的函数wrapper
。在wrapper
函数中,我们首先检查当前用户的角色是否在允许访问该页面的角色列表中,如果是,则调用原始函数func
并将它的参数传递给它,否则抛出一个ValueError
异常。
现在,我们可以将这两个装饰器应用到我们的页面处理函数中。例如:
def authenticate(func):
def wrapper(*args, **kwargs):
# before
print("authenticate before...")
#假设在args[0]中保存了当前用户的信息
if args[0].is_authenticated:
# after
print("authenticate after...")
result = func(*args, **kwargs)
return result
else:
# after
print("authenticate after...")
raise ValueError("用户未认证:User is not authenticated")
# 返回包装后的函数
return wrapper
def authorize(roles):
def decorator(func):
def wrapper(*args, **kwargs):
# 假设在args[0]中保存了当前用户的信息
if args[0].role in roles:
result = func(*args, **kwargs)
return result
else:
# raise ValueError("用户未授权:User is not authorized")
print("用户未授权")
# 返回包装后的函数
return wrapper
return decorator
class User():
def __init__(self, name, role):
self.name = name
self.role = role
self.is_authenticated = True
@authenticate
@authorize(["admin", "editor"])
def secret_page(user):
return "This is the secret page"
user = User("Alice", "admin")
res = secret_page(user)
print(res)
user = User("Tom", "guest")
res = secret_page(user)
# print(res)
运行结果:
authenticate before...
authenticate after...
This is the secret page
authenticate before...
authenticate after...
用户未授权
在这个例子中,我们定义了一个User
类,其中包含用户的名称、角色和身份验证状态。然后,我们将authenticate
和authorize
装饰器应用到secret_page
函数中:authenticate
用于验证用户身份,authorize
用于授权用户访问该页面。在执行secret_page(user)
时,Python会按照从上到下的顺序依次应用装饰器,并检查当前用户的身份和角色是否满足要求。如果满足要求,则输出页面内容,否则抛出异常。
以上就是使用Python元编程装饰器实现身份验证和授权功能的一个简单例子。当然,在实际开发中,我们需要更加细致地设计和实现这些功能,以确保应用程序的安全性和可靠性。
2.实现缓存功能
下面以一个简单的例子来说明如何使用Python元编程装饰器实现缓存功能。
假设我们有一个函数calculate
,它接受一个整数作为参数,并计算该整数的阶乘。现在,我们希望使用装饰器来缓存该函数的计算结果,以提高程序的性能。
首先,我们可以定义一个装饰器函数cache
,用于缓存函数调用的结果:
在这个装饰器函数中,我们首先定义一个cache_dict
字典,用于保存函数调用的结果。然后,我们返回一个新的函数wrapper
,它接受任意数量的参数*args
,这些参数将被传递给原始函数func
。在wrapper
函数中,我们首先检查参数args
是否已经在cache_dict
中,如果是,则直接返回该结果;否则,调用原始函数func
,将结果保存在cache_dict
中,并返回该结果。
接下来,我们可以将cache
装饰器应用到calculate
函数中。例如:
def cache(func):
cache_dict = {}
def wrapper(*args, **kwargs):
if args in cache_dict:
return cache_dict[args]
else:
result = func(*args)
cache_dict[args] = result
return result
return wrapper
@cache
def calculate(n):
if n == 0:
return 1
else:
return n * calculate(n-1)
res = calculate(5) # 第一次调用,需要计算并缓存结果
print(res)
res = calculate(5) #第二次调用,直接从缓存中获取结果
print(res)
运行结果:
120
120
在这个例子中,我们定义了一个calculate
函数,它计算给定整数的阶乘。然后,我们将cache
装饰器应用到该函数中,以实现缓存功能。在执行calculate(n)
时,Python会自动按照装饰器的规则调用cache(wrapper)
函数,将原始的calculate
函数包装成一个新的函数,并返回该函数对象。在第一次调用calculate(5)
时,Python会先调用wrapper(5)
函数,并将结果缓存在cache_dict
中。在第二次调用calculate(5)
时,Python直接从cache_dict
中获取结果,并返回该结果。这样,整个计算过程只需要进行一次,大大提高了程序的性能。
以上就是使用Python元编程装饰器实现缓存功能的一个简单例子。当然,在实际开发中,我们需要更加细致地设计和实现缓存策略,以确保缓存的准确性和有效性。
3.实现日志输出功能
下面以一个简单的例子来说明如何使用Python元编程装饰器实现处理日志输出功能。
假设我们有一个函数calculate
,它接受一个整数作为参数,并计算该整数的阶乘。现在,我们希望在调用该函数时自动记录日志输出,以便于调试和监控程序的运行情况。
首先,我们可以定义一个装饰器函数log
,用于记录函数调用和返回结果:
在这个装饰器函数中,我们首先使用Python标准库中的logging
模块来设置日志输出格式和目标文件。然后,我们返回一个新的函数wrapper
,它接受任意数量的位置参数*args
和关键字参数**kwargs
,这些参数将被传递给原始函数func
。在wrapper
函数中,我们首先使用logging.debug()
函数记录函数调用的信息,包括函数名、位置参数和关键字参数。然后,调用原始函数func
,将结果保存在result
变量中。最后,再使用logging.debug()
函数记录函数返回的信息,包括函数名和返回结果。
接下来,我们可以将log
装饰器应用到calculate
函数中。例如:
import logging
import os
logging.basicConfig(level=logging.DEBUG,
format="%(asctime)s %(filename)s[line:%(lineno)d] %(levelname)s %(message)s",
# datefmt="%a, %d, %b, %Y %H:%M:%S", # 精确到秒
datefmt='%Y-%m-%d %H:%M:%S', # 精确到秒
filename="output.log",
filemode="w")
def log(func):
def wrapper(*args, **kwargs):
logging.debug('Calling function {} with args: {}, kwargs: {}'.format(func.__name__, args, kwargs))
result = func(*args, **kwargs)
logging.debug('Function {} returned: {}'.format(func.__name__, result))
return result
return wrapper
@log
def calculate(n):
if n == 0:
return 1
else:
time.sleep(0.5)
return n * calculate(n - 1)
# 调用函数,同时记录输出
print(calculate(5))
print(os.getcwd()) #output.log文件位置
运行结果:
120
D:\code\AutoTest\common\Metaprogramming
output.log
在这个例子中,我们定义了一个calculate
函数,它计算给定整数的阶乘。然后,我们将log
装饰器应用到该函数中,以实现记录日志输出功能。在执行calculate(n)
时,Python会自动按照装饰器的规则调用log(wrapper)
函数,将原始的calculate
函数包装成一个新的函数,并返回该函数对象。在调用calculate(5)
时,Python会自动调用wrapper(5)
函数,并记录函数调用和返回的信息到指定的日志文件中。这样,我们就可以方便地查看函数的调用过程和计算结果,从而提高程序的可读性和可维护性。
以上就是使用Python元编程装饰器实现处理日志输出功能的一个简单例子。当然,在实际开发中,我们还需要更加细致地设计和实现日志输出策略,以满足实际的需求。
三、附录
1. logging.basicConfig介绍
logging是Python标准库中的一个模块,用于记录应用程序运行时的日志信息。其中,basicConfig函数是logging模块中的一个配置函数,用于对logging进行基本配置。
basicConfig函数可以接受多个参数,用于设置日志的格式、日志级别、输出位置等信息。常用的参数如下:
- level:设置日志级别,可选值有DEBUG、INFO、WARNING、ERROR和CRITICAL,默认为WARNING。
- format:设置日志格式,可选值有asctime、name、levelname、message等参数,例如:'[%(asctime)s] [%(levelname)s] %(message)s'。
- filename:指定日志输出到文件,如果不指定则默认输出到控制台。
- filemode:指定日志输出文件的打开模式,可选值有'w'、'a'等。
下面是一个简单的例子:
import logging
logging.basicConfig(level=logging.INFO, format='%(asctime)s %(levelname)s %(message)s')
logging.info('info message')
logging.warning('warning message')
这段代码设置了日志级别为INFO,格式为'时间 日志级别 日志信息',并输出了一个INFO级别的日志和一个WARNING级别的日志。输出结果如下:
2021-09-30 14:20:10,315 INFO info message
2021-09-30 14:20:10,315 WARNING warning message
注意,如果需要对不同的模块分别进行日志记录,则需要创建不同的Logger对象,并对其进行配置。可以使用Logger类中的方法来设置日志级别、日志格式、输出位置等信息,例如addHandler方法可以添加一个处理器来指定日志输出到文件或网络等位置。
2. 精确到毫秒,打印时间
方法一:使用datetime
你可以使用 datetime
模块来输出当前时间精确到毫秒:
from datetime import datetime
now = datetime.now()
micro_sec = now.microsecond // 1000 # 计算毫秒部分
print(now.strftime('%Y-%m-%d %H:%M:%S.') + str(micro_sec).zfill(3))
在这个代码中,我们使用 datetime.now()
函数获取当前时间,然后使用 microsecond
属性获取微秒部分,并将其除以 1000 得到毫秒。最后,我们将毫秒部分用 zfill(3)
进行前导零填充,并拼接成完整的时间字符串。
方法二:使用time
import time
t = time.time()
s = time.strftime('%Y-%m-%d %H:%M:%S.', time.localtime(t)) + ("%d" % (t % 1 * 1000)).zfill(3)
print(s)
在这个代码中,我们没有使用 %f
,而是手动计算并格式化毫秒部分。具体来说,我们使用了时间戳 t
对 1 取模获得秒的小数部分,乘以 1000 得到毫秒,再使用 zfill(3)
来对毫秒部分进行前导零填充。
这种方式需要手动计算毫秒部分,并且可能会有一些精度损失,但是在 Python 2.x 中可以工作。