在类级变量上使用Python装饰器作为反射元数据?

6 投票
1 回答
2466 浏览
提问于 2025-04-17 14:11

我有一个Python类(其实是一个Google App Engine模型),它看起来像这样:

class MyClass(ParentClass):
    variable_one = PropertyClass(...)
    variable_two = PropertyClass(...)

我打算建立一个系统,可以描述这个类的两个实例之间的区别,这两个实例代表同一个实体在不同时间点的两个版本。最终,它会生成一个像这样的字符串:

"Variable One changed from 'foo' to 'bar', Variable Two changed from empty to 'baz'"

所以,我需要某个地方来存储“variable_one”应该显示为“Variable One”等等的信息。由于我之前使用C#,我的直觉是使用属性(Attributes),比如:

class MyClass : ParentClass
{
    [DisplayName("Variable One")]
    public PropertyClass variable_one;

    [DisplayName("Variable Two")]
    public PropertyClass variable_two;
}

一开始,Python中类似的东西似乎是装饰器(decorators):

class MyClass(ParentClass):
    @display_name("Variable One")
    variable_one = PropertyClass(...)

    @display_name("Variable Two")
    variable_two = PropertyClass(...)

不过,装饰器实际上是在做函数柯里化(function currying),而类变量的定义只是把一个PropertyClass的实例赋值给variable_one和variable_two,所以这样看起来在Python中不太有效。无论如何,我不太确定该怎么写。

一个选择似乎是扩展PropertyClass……

class MyClass(ParentClass):
    variable_one = MyPropertyClass("Variable One", ...)
    variable_two = MyPropertyClass("Variable Two", ...)

但有几个不同的属性类正在使用,我想给它们都添加一个共同的行为,这样做似乎太麻烦了。

我想我可以定义一个字典:

class MyClass(ParentClass):
    variable_one = PropertyClass(...)
    variable_two = PropertyClass(...)
    variable_display_names = {
        "variable_one": "Variable One",
        "variable_two": "Variable Two"
    }

但我希望能听听一些更有经验的Python开发者,关于实现这个目标的最佳、最符合Python风格的方法。

1 个回答

5

数据存储的属性类就像是描述符,类似于Python中的@property装饰器,但它们是自定义的类。如果这些类没有实现__setattr__方法,你就可以在这些对象上设置任意属性:

 class MyClass(ParentClass):
     variable_one = PropertyClass(...)
     variable_one.display_name = "Variable One"

     variable_two = PropertyClass(...)
     variable_two.display_name = "Variable Two"

这个可以推广成一个装饰器,写成:

def display_name(dispname):
    def decorate_with_display_name(prop):
        prop.display_name = dispname
        return prop
    return decorate_with_display_name

只要你通过类来访问这个属性,而不是通过实例,你就可以再次获取这些值;比如给定一个实例,你可以这样做print type(instance).variable_two.display_name

撰写回答