【发布时间】:2021-10-23 05:45:22
【问题描述】:
考虑以下元类/类定义:
class Meta(type):
"""A python metaclass."""
def greet_user(cls):
"""Print a friendly greeting identifying the class's name."""
print(f"Hello, I'm the class '{cls.__name__}'!")
class UsesMeta(metaclass=Meta):
"""A class that uses `Meta` as its metaclass."""
As we know,在一个元类中定义一个方法,表示它被类继承,并且可以被类使用。这意味着交互式控制台中的以下代码可以正常工作:
>>> UsesMeta.greet_user()
Hello, I'm the class 'UsesMeta'!
但是,这种方法的一个主要缺点是我们可能包含在方法定义中的任何文档都丢失了。如果我们在交互式控制台中输入help(UsesMeta),我们会看到没有对方法greet_user 的引用,更不用说我们在方法定义中放入的文档字符串了:
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)
现在当然是is writable 类的__doc__ 属性,因此一种解决方案是重写元类/类定义,如下所示:
from pydoc import render_doc
from functools import cache
def get_documentation(func_or_cls):
"""Get the output printed by the `help` function as a string"""
return '\n'.join(render_doc(func_or_cls).splitlines()[2:])
class Meta(type):
"""A python metaclass."""
@classmethod
@cache
def _docs(metacls) -> str:
"""Get the documentation for all public methods and properties defined in the metaclass."""
divider = '\n\n----------------------------------------------\n\n'
metacls_name = metacls.__name__
metacls_dict = metacls.__dict__
methods_header = (
f'Classmethods inherited from metaclass `{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'Classmethod properties inherited from metaclass `{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):
"""Make a new class, but tweak `.__doc__` so it includes information about the metaclass's methods."""
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 a friendly greeting identifying the class's name."""
print(f"Hello, I'm the class '{cls.__name__}'!")
class UsesMeta(metaclass=Meta):
"""A class that uses `Meta` as its metaclass."""
这“解决”了问题;如果我们现在在交互式控制台中输入help(UsesMeta),那么从Meta 继承的方法现在已被完整记录:
Help on class UsesMeta in module __main__:
class UsesMeta(builtins.object)
| A class that uses `Meta` as its metaclass.
|
| ----------------------------------------------
|
| Classmethods inherited from metaclass `Meta`
|
| greet_user(cls)
| Print a friendly greeting identifying the class's name.
|
| ----------------------------------------------
|
| Classmethod properties inherited from metaclass `Meta`
|
|
|
| ----------------------------------------------
|
| Data descriptors defined here:
|
| __dict__
| dictionary for instance variables (if defined)
|
| __weakref__
| list of weak references to the object (if defined)
然而,要实现这个目标需要大量的代码。 有没有更好的办法?
标准库是如何做到的?
我也很好奇标准库中某些类的管理方式。如果我们有这样的Enum 定义:
from enum import Enum
class FooEnum(Enum):
BAR = 1
然后,在交互式控制台中输入help(FooEnum) 包括这个sn-p:
| ----------------------------------------------------------------------
| 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 模块究竟是如何实现这一点的?
我在这里使用元类,而不是仅仅在类定义的主体中定义classmethods 的原因
您可能会在元类中编写一些方法,例如 __iter__、__getitem__ 或 __len__、can't be written 和 classmethods,但如果您在元类中定义它们,可能会产生极具表现力的代码. enum 模块是这个的 excellent example。
【问题讨论】:
标签: python python-3.x metaclass class-method docstring