Python中如何为从元类继承的方法生成可被help()识别的文档?
问题背景
先给出初始的元类与业务类定义:
class Meta(type): """Python元类定义""" def greet_user(cls): """打印友好问候,展示当前类名""" print(f"Hello, I'm the class '{cls.__name__}'!") class UsesMeta(metaclass=Meta): """使用Meta作为元类的业务类"""
元类中定义的方法会被使用该元类的类继承,可直接通过类调用,控制台运行测试代码效果如下:
>>> UsesMeta.greet_user() Hello, I'm the class 'UsesMeta'!
存在的缺陷
该方案的重大问题是元类方法的文档不会出现在子类的help输出里。运行help(UsesMeta)的输出如下,完全找不到greet_user方法的引用,更不用说对应的docstring了:
Help on class UsesMeta in module __main__: class UsesMeta(builtins.object) | A class that uses `Meta` as its metaclass. | | Data descriptors defined here: | | __dict__ | dictionary for instance variables (if defined) | | __weakref__ | list of weak references to the object (if defined)
现有解决方案
因为类的__doc__属性是可写的,所以可以重写元类逻辑,把元类方法的文档手动拼接进子类的__doc__里,实现代码如下:
from pydoc import render_doc from functools import cache def get_documentation(func_or_cls): """将help函数的输出转换为字符串返回""" return '\n'.join(render_doc(func_or_cls).splitlines()[2:]) class Meta(type): """Python元类定义""" @classmethod @cache def _docs(metacls) -> str: """获取元类中所有公开方法和属性的文档""" divider = '\n\n----------------------------------------------\n\n' metacls_name = metacls.__name__ metacls_dict = metacls.__dict__ methods_header = ( f'从元类`{metacls_name}`继承的类方法' f'\n\n' ) method_docstrings = '\n\n'.join( get_documentation(method) for method_name, method in metacls_dict.items() if not (method_name.startswith('_') or isinstance(method, property)) ) properties_header = ( f'从元类`{metacls_name}`继承的类属性' f'\n\n' ) properties_docstrings = '\n\n'.join( f'{property_name}\n{get_documentation(prop)}' for property_name, prop in metacls_dict.items() if isinstance(prop, property) and not property_name.startswith('_') ) return ''.join(( divider, methods_header, method_docstrings, divider, properties_header, properties_docstrings, divider )) def __new__(metacls, cls_name, cls_bases, cls_dict): """创建新类时,将元类的方法文档拼接进新类的__doc__中""" new = super().__new__(metacls, cls_name, cls_bases, cls_dict) metacls_docs = metacls._docs() if new.__doc__ is None: new.__doc__ = metacls_docs else: new.__doc__ += metacls_docs return new def greet_user(cls): """打印友好问候,展示当前类名""" print(f"Hello, I'm the class '{cls.__name__}'!") class UsesMeta(metaclass=Meta): """使用Meta作为元类的业务类"""
该方案可以实现需求,运行help(UsesMeta)就能看到元类的方法文档了,输出如下:
Help on class UsesMeta in module __main__: class UsesMeta(builtins.object) | A class that uses `Meta` as its metaclass. | | ---------------------------------------------- | | 从元类`Meta`继承的类方法 | | greet_user(cls) | 打印友好问候,展示当前类名 | | ---------------------------------------------- | | 从元类`Meta`继承的类属性 | | | | ---------------------------------------------- | | Data descriptors defined here: | | __dict__ | dictionary for instance variables (if defined) | | __weakref__ | list of weak references to the object (if defined)
但该方案需要编写的额外代码量太大,有没有更简洁的实现方式?
标准库的实现参考
Python标准库的Enum模块不存在这个问题,如下定义的枚举类:
from enum import Enum class FooEnum(Enum): BAR = 1
运行help(FooEnum)的输出会包含元类继承的属性文档:
| ---------------------------------------------------------------------- | Readonly properties inherited from enum.EnumMeta: | | __members__ | Returns a mapping of member name->value. | | This mapping lists all enum members, including aliases. Note that this | is a read-only view of the internal mapping.
enum模块是如何实现该功能的?
使用元类而非类方法的原因
类似__iter__、__getitem__、__len__这类特殊方法无法被定义为类方法,在元类中定义这些方法可以实现更灵活的功能,enum模块就是典型的应用案例。
内容的提问来源于stack exchange,提问作者Alex Waygood
相关产品推荐
相关产品推荐

