MST

星途 面试题库

面试题:Python属性描述符在元类中的复杂应用

设计一个元类,在元类中利用属性描述符来自动为类的所有方法添加日志记录功能,记录方法的调用时间、参数和返回值。要求给出完整的代码实现,并详细解释每一步的设计思路和原理。
40.7万 热度难度
编程语言Python

知识考点

AI 面试

面试题答案

一键面试
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)

设计思路和原理

  1. 属性描述符 MethodLogger

    • __init__ 方法:在初始化时接收被装饰的函数 func,将其保存为实例属性。
    • __get__ 方法:这个方法使得描述符可以在类的实例获取属性时被调用。这里返回一个 wrapper 函数。
    • wrapper 函数:在这个函数内部,记录方法调用的开始时间 start_time,调用原始函数 self.func 并获取返回值 result,记录结束时间 end_time,然后打印出方法调用的时间、参数和返回值信息,最后返回 result
  2. 元类 LoggerMeta

    • __new__ 方法__new__ 方法是元类中用于创建类对象的方法。在这里遍历类的命名空间 namespace
    • 筛选方法:通过 callable(attr_value) and not attr_name.startswith('__') 来判断属性是否是一个方法(可调用且不是魔术方法)。
    • 添加日志功能:对于符合条件的方法,使用 MethodLogger 进行包装,将包装后的对象重新赋值回命名空间。
    • 创建类对象:最后通过调用 super().__new__(mcs, name, bases, namespace) 使用默认的类创建机制创建新的类对象。
  3. 使用元类的类 MyClass

    • 这个类通过 metaclass=LoggerMeta 指定使用 LoggerMeta 作为元类。
    • 当创建 MyClass 的实例并调用其方法时,由于元类的作用,这些方法已经被 MethodLogger 包装,从而实现了日志记录功能。