如何检查 class X 是否是 class Y 中的嵌套 class?

How to check if class X is a nested class in class Y?

我目前正在开发 CLI abstraction layer,它将 CLI 程序抽象为 Python 中的 classes。这样的 CLI 程序提供了一种结构化的方式来启用和配置 CLI 参数。它有助于检查错误的输入并生成正确转义的参数(例如添加双引号)。

注意:下面的例子使用的是Git,而在我的target application中,它将是商业工具,不提供任何PythonAPI 或类似的。

基本思路:

tool = Git()
tool[tool.FlagVersion] = True

print(tool.ToArgumentList())

一些Python代码:

from pyAttributes import Attribute

class CLIOption(Attribute): ...   # see pyAttributes for more details

class Argument:
  _name: ClassVar[str]

  def __init_subclass__(cls, *args, name: str = "", **kwargs):
    super().__init_subclass__(*args, **kwargs)
    cls._name = name

class FlagArgument(Argument): ...

class CommandArgument(Argument): ...

class Program:
  __cliOptions__: Dict[Type[Argument], Optional[Argument]]

  def __init_subclass__(cls, *args, **kwargs):
    """Hook into subclass creation to register all marked CLI options in ``__cliOptions__``.
    super().__init_subclass__(*args, **kwargs)

    # get all marked options and 
    cls.__cliOptions__ = {}
    for option in CLIOption.GetClasses():
      cls.__cliOptions__[option] = None

class Git(Program):
  @CLIOption()
  class FlagVersion(FlagArgument, name="--version"): ...

  @CLIOption()
  class FlagHelp(FlagArgument, name="--help"): ...

  @CLIOption()
  class CmdCommit(CommandArgument, name="commit"): ...

观察:

主要问题:

  1. 如何检查 class FlagVersion 是否是 class Git 的嵌套 class?

目前我调查的内容:

所以我看到一个可能的“丑陋”解决方案,使用 __qualname__ 和字符串操作来检查 class 是否嵌套以及它是否嵌套在某个外部范围内。

算法:

如果一个嵌套 class 在 parent class 中定义,而另一个嵌套 class 在派生 class 中定义,这将变得更加复杂.然后我还需要研究 MRO ... oOo

次要问题:

  1. 有没有比使用字符串操作更好的方法?
  2. Python data model 不应该提供一种更好的方式来获取这些信息吗?

因为我不太懂英文,我通过翻译理解是,你需要了解如何在子[=23=中获得由CLIOption修饰的嵌入class ] 的 Program(此处为 Git)。如果是这样,以下方法可能对您有所帮助。

我读了一些pyAttributes

的代码
from pyAttributes import Attribute

class Program(object):
    __cliOptions__: Dict[Type[Argument], Optional[Argument]]

    def __init_subclass__(cls, *args, **kwargs):
        cls.__cliOptions__ = {}
        for obj in cls.__dict__.values():
            if hasattr(obj, Attribute.__AttributesMemberName__):
                print(obj)
        # for option in CLIOption.GetClasses():
        #     cls.__cliOptions__[option] = None


class Git(Program):
    a = 1
    b = 2

    @CLIOption()
    class FlagVersion(FlagArgument, name="--version"):
        ...

    @CLIOption()
    class FlagHelp(FlagArgument, name="--help"):
        ...

当然上面的不能直接用。后来发现Attribute._AppendAttribute方法当然有错误,如下,我修改了


class CLIOption(Attribute):
    ...  # see pyAttributes for more details

    @staticmethod
    def _AppendAttribute(func: Callable, attribute: 'Attribute') -> None:
        # inherit attributes and prepend myself or create a new attributes list
        if Attribute.__AttributesMemberName__ in func.__dict__:
            func.__dict__[Attribute.__AttributesMemberName__].insert(0, attribute)
        else:
            # The original func.__setattr__(Attribute.__AttributesMemberName__, [attribute]) has an error
            # Because __setattr__ of class FlagVersion is object.__setattr__

            setattr(func, Attribute.__AttributesMemberName__, [attribute])
            # or object.__setattr__(func, Attribute.__AttributesMemberName__, [attribute])

通过迭代 __dict__ 遵循建议的方法效果很好。

所以这是第一个基于给定想法开发的解决方案:

def isnestedclass(cls: Type, scope: Type) -> bool:
    for memberName in scope.__dict__:
        member = getattr(scope, memberName)
        if type(member) is type:
            if cls is member:
                return True

    return False

该解决方案不适用于从父 类 继承的成员。 所以我通过 mro().

搜索继承图来扩展它

这是我对 isnestedclass 辅助函数的当前和最终解决方案。

def isnestedclass(cls: Type, scope: Type) -> bool:
    for mroClass in scope.mro():
        for memberName in mroClass.__dict__:
            member = getattr(mroClass, memberName)
            if type(member) is type:
                if cls is member:
                    return True

    return False

该函数在 pyTooling 包中可用。