面试题答案
一键面试import time
class MethodLogger:
def __init__(self, func):
self.func = func
def __get__(self, instance, owner):
def wrapper(*args, **kwargs):
start_time = time.time()
result = self.func(instance, *args, **kwargs)
end_time = time.time()
print(f"Method {self.func.__name__} called at {time.ctime(start_time)}, "
f"with args: {args}, kwargs: {kwargs}, "
f"returned: {result} in {end_time - start_time} seconds")
return result
return wrapper
class LoggerMeta(type):
def __new__(mcs, name, bases, namespace):
for attr_name, attr_value in namespace.items():
if callable(attr_value) and not attr_name.startswith('__'):
namespace[attr_name] = MethodLogger(attr_value)
return super().__new__(mcs, name, bases, namespace)
class MyClass(metaclass=LoggerMeta):
def add(self, a, b):
return a + b
if __name__ == "__main__":
my_obj = MyClass()
my_obj.add(2, 3)
设计思路和原理
-
属性描述符
MethodLogger
:__init__
方法:在初始化时接收被装饰的函数func
,将其保存为实例属性。__get__
方法:这个方法使得描述符可以在类的实例获取属性时被调用。这里返回一个wrapper
函数。wrapper
函数:在这个函数内部,记录方法调用的开始时间start_time
,调用原始函数self.func
并获取返回值result
,记录结束时间end_time
,然后打印出方法调用的时间、参数和返回值信息,最后返回result
。
-
元类
LoggerMeta
:__new__
方法:__new__
方法是元类中用于创建类对象的方法。在这里遍历类的命名空间namespace
。- 筛选方法:通过
callable(attr_value) and not attr_name.startswith('__')
来判断属性是否是一个方法(可调用且不是魔术方法)。 - 添加日志功能:对于符合条件的方法,使用
MethodLogger
进行包装,将包装后的对象重新赋值回命名空间。 - 创建类对象:最后通过调用
super().__new__(mcs, name, bases, namespace)
使用默认的类创建机制创建新的类对象。
-
使用元类的类
MyClass
:- 这个类通过
metaclass=LoggerMeta
指定使用LoggerMeta
作为元类。 - 当创建
MyClass
的实例并调用其方法时,由于元类的作用,这些方法已经被MethodLogger
包装,从而实现了日志记录功能。
- 这个类通过