用于保存关于 类 的元数据的装饰器替代品
Alternatives to decorators for saving metadata about classes
我正在编写一个 GUI 库,我想让程序员提供关于他们的程序的元信息,我可以用它来微调 GUI。我打算为此目的使用函数装饰器,例如:
class App:
@Useraction(description='close the program', hotkey='ctrl+q')
def quit(self):
sys.exit()
问题是此信息需要绑定到相应的 class。例如,如果程序是一个图像编辑器,它可能有一个 Image
class 提供更多的用户操作:
class Image:
@Useraction(description='invert the colors')
def invert_colors(self):
...
但是,由于未绑定方法的概念已在 python 3 中删除,因此似乎没有办法找到函数的定义 class。 (我找到了 this old answer,但这在装饰器中不起作用。)
那么,既然装饰器看起来无法正常工作,那么最好的方法是什么?我想避免使用
这样的代码
class App:
def quit(self):
sys.exit()
Useraction(App.quit, description='close the program', hotkey='ctrl+q')
如果可能的话。
为了完整起见,@Useraction
装饰器看起来有点像这样:
class_metadata= defaultdict(dict)
def Useraction(**meta):
def wrap(f):
cls= get_defining_class(f)
class_metadata[cls][f]= meta
return f
return wrap
我找到了一种让装饰器与 inspect
模块一起工作的方法,但这不是一个很好的解决方案,所以我仍然愿意接受更好的建议。
基本上我正在做的是遍历解释器堆栈,直到找到当前 class。由于此时不存在 class 对象,我改为提取 class 的质量名称和模块。
import inspect
def get_current_class():
"""
Returns the name of the current module and the name of the class that is currently being created.
Has to be called in class-level code, for example:
def deco(f):
print(get_current_class())
return f
def deco2(arg):
def wrap(f):
print(get_current_class())
return f
return wrap
class Foo:
print(get_current_class())
@deco
def f(self):
pass
@deco2('foobar')
def f2(self):
pass
"""
frame= inspect.currentframe()
while True:
frame= frame.f_back
if '__module__' in frame.f_locals:
break
dict_= frame.f_locals
cls= (dict_['__module__'], dict_['__qualname__'])
return cls
然后在某种 post-processing 步骤中,我使用模块和 class 名称来查找实际的 class 对象。
def postprocess():
global class_metadata
def findclass(module, qualname):
scope= sys.modules[module]
for name in qualname.split('.'):
scope= getattr(scope, name)
return scope
class_metadata= {findclass(cls[0], cls[1]):meta for cls,meta in class_metadata.items()}
此解决方案的问题是 class 查找延迟。如果 classes 被覆盖或删除,post-processing 步骤将找到错误的 class 或完全失败。示例:
class C:
@Useraction(hotkey='ctrl+f')
def f(self):
print('f')
class C:
pass
postprocess()
您正在使用装饰器将元数据添加到方法中。那也行。可以做到,例如这样:
def user_action(description):
def decorate(func):
func.user_action = {'description': description}
return func
return decorate
现在,您想要收集该数据并将其以 class_metadata[cls][f]= meta
的形式存储在全局字典中。为此,您需要找到所有装饰方法及其 classes.
最简单的方法可能是使用元classes。在 metaclass 中,您可以定义创建 class 时发生的情况。在这种情况下,遍历class的所有方法,找到修饰的方法并将它们存储在字典中:
class UserActionMeta(type):
user_action_meta_data = collections.defaultdict(dict)
def __new__(cls, name, bases, attrs):
rtn = type.__new__(cls, name, bases, attrs)
for attr in attrs.values():
if hasattr(attr, 'user_action'):
UserActionMeta.user_action_meta_data[rtn][attr] = attr.user_action
return rtn
我将全局词典 user_action_meta_data
放在元 class 中只是因为它感觉合乎逻辑。它可以在任何地方。
现在,只需在任何 class:
中使用它
class X(metaclass=UserActionMeta):
@user_action('Exit the application')
def exit(self):
pass
静态 UserActionMeta.user_action_meta_data
现在包含您想要的数据:
defaultdict(<class 'dict'>, {<class '__main__.X'>: {<function exit at 0x00000000029F36C8>: {'description': 'Exit the application'}}})
我正在编写一个 GUI 库,我想让程序员提供关于他们的程序的元信息,我可以用它来微调 GUI。我打算为此目的使用函数装饰器,例如:
class App:
@Useraction(description='close the program', hotkey='ctrl+q')
def quit(self):
sys.exit()
问题是此信息需要绑定到相应的 class。例如,如果程序是一个图像编辑器,它可能有一个 Image
class 提供更多的用户操作:
class Image:
@Useraction(description='invert the colors')
def invert_colors(self):
...
但是,由于未绑定方法的概念已在 python 3 中删除,因此似乎没有办法找到函数的定义 class。 (我找到了 this old answer,但这在装饰器中不起作用。)
那么,既然装饰器看起来无法正常工作,那么最好的方法是什么?我想避免使用
这样的代码class App:
def quit(self):
sys.exit()
Useraction(App.quit, description='close the program', hotkey='ctrl+q')
如果可能的话。
为了完整起见,@Useraction
装饰器看起来有点像这样:
class_metadata= defaultdict(dict)
def Useraction(**meta):
def wrap(f):
cls= get_defining_class(f)
class_metadata[cls][f]= meta
return f
return wrap
我找到了一种让装饰器与 inspect
模块一起工作的方法,但这不是一个很好的解决方案,所以我仍然愿意接受更好的建议。
基本上我正在做的是遍历解释器堆栈,直到找到当前 class。由于此时不存在 class 对象,我改为提取 class 的质量名称和模块。
import inspect
def get_current_class():
"""
Returns the name of the current module and the name of the class that is currently being created.
Has to be called in class-level code, for example:
def deco(f):
print(get_current_class())
return f
def deco2(arg):
def wrap(f):
print(get_current_class())
return f
return wrap
class Foo:
print(get_current_class())
@deco
def f(self):
pass
@deco2('foobar')
def f2(self):
pass
"""
frame= inspect.currentframe()
while True:
frame= frame.f_back
if '__module__' in frame.f_locals:
break
dict_= frame.f_locals
cls= (dict_['__module__'], dict_['__qualname__'])
return cls
然后在某种 post-processing 步骤中,我使用模块和 class 名称来查找实际的 class 对象。
def postprocess():
global class_metadata
def findclass(module, qualname):
scope= sys.modules[module]
for name in qualname.split('.'):
scope= getattr(scope, name)
return scope
class_metadata= {findclass(cls[0], cls[1]):meta for cls,meta in class_metadata.items()}
此解决方案的问题是 class 查找延迟。如果 classes 被覆盖或删除,post-processing 步骤将找到错误的 class 或完全失败。示例:
class C:
@Useraction(hotkey='ctrl+f')
def f(self):
print('f')
class C:
pass
postprocess()
您正在使用装饰器将元数据添加到方法中。那也行。可以做到,例如这样:
def user_action(description):
def decorate(func):
func.user_action = {'description': description}
return func
return decorate
现在,您想要收集该数据并将其以 class_metadata[cls][f]= meta
的形式存储在全局字典中。为此,您需要找到所有装饰方法及其 classes.
最简单的方法可能是使用元classes。在 metaclass 中,您可以定义创建 class 时发生的情况。在这种情况下,遍历class的所有方法,找到修饰的方法并将它们存储在字典中:
class UserActionMeta(type):
user_action_meta_data = collections.defaultdict(dict)
def __new__(cls, name, bases, attrs):
rtn = type.__new__(cls, name, bases, attrs)
for attr in attrs.values():
if hasattr(attr, 'user_action'):
UserActionMeta.user_action_meta_data[rtn][attr] = attr.user_action
return rtn
我将全局词典 user_action_meta_data
放在元 class 中只是因为它感觉合乎逻辑。它可以在任何地方。
现在,只需在任何 class:
中使用它class X(metaclass=UserActionMeta):
@user_action('Exit the application')
def exit(self):
pass
静态 UserActionMeta.user_action_meta_data
现在包含您想要的数据:
defaultdict(<class 'dict'>, {<class '__main__.X'>: {<function exit at 0x00000000029F36C8>: {'description': 'Exit the application'}}})