首页 > 解决方案 > 我可以从 python 中的方法装饰器编辑类变量吗?

问题描述

我有一堂课如下:

class Hamburger():
    topping_methods = {}

    @prepare_topping("burger", "onion_ring")
    def cook(self, stuff, temp):
        print("cooking")

h = Hamburger()

我希望 prepare_topping 装饰器添加条目Hamburger.topping_methods,使其看起来像:

{"burger":< cook method reference >, "onion_ring": < cook method reference >}

关键是我需要在类的初始化程序运行之前发生这种情况(真正的用例是 dict 用于在初始化程序中注册事件回调。)但是因为我想访问类变量,它需要在定义类之后。

这是我对装饰器逻辑的了解:

def prepare_topping(*args):
    def deco(func):
        # This is when I want to add to class dict, but cannot access method arguments yet
        print(eval(func.__qualname__.split(".")[0]).topping_methods)  # Gets class that method belongs to in a hacky way, but the class is not yet defined
        def wrapper(self, *args):
            print(self.topping_methods)  # Only run if the method is called, which is after __init__
            return func(self, *args)
        return wrapper
    return deco

我意识到我将永远无法访问方法self参数来实现这一点,因为无论该方法是否实际被调用,我都想做一些事情。有没有一种方法可以让装饰器仅在定义类之后运行?在仍然使用装饰器的同时还有其他方法可以实现这一点吗?

标签: pythonpython-3.xdecoratorpython-decorators

解决方案


您可以使用超类和__init_subclass__挂钩来连接:

class CookeryClass:
    topping_methods: dict

    def __init_subclass__(cls, **kwargs):
        cls.topping_methods = {}
        for obj in vars(cls).values():
            if hasattr(obj, "topping_keys"):
                for key in obj.topping_keys:
                    cls.topping_methods[key] = obj


def prepare_topping(*keys):
    def decorator(func):
        func.topping_keys = keys
        return func

    return decorator


class Hamburger(CookeryClass):
    @prepare_topping("burger", "onion_ring")
    def cook(self, stuff, temp):
        print("cooking")

    @prepare_topping("mayonnaise", "pineapples")
    def not_on_a_pizza_surely(self, stuff, temp):
        print("cooking")


print(Hamburger.topping_methods)

这打印出来

{
  'burger': <function Hamburger.cook at 0x000001EA49D293A0>,
  'onion_ring': <function Hamburger.cook at 0x000001EA49D293A0>,
  'mayonnaise': <function Hamburger.not_on_a_pizza_surely at 0x000001EA49D29430>,
  'pineapples': <function Hamburger.not_on_a_pizza_surely at 0x000001EA49D29430>,
}

推荐阅读