如何检查 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 或类似的。
基本思路:
- 工具 Git 的抽象声明了一个
Git
class,它派生自 class Program
.
这个parentclass实现了所有程序的通用方法
- CLI 选项在
Git
class. 上表示为 嵌套 class 定义
- 嵌套的 classes 标记有 class-based 装饰器
CLIOption
来自 Attribute
(有关详细信息,请参阅 https://github.com/pyTooling/pyAttributes)
- 可以通过索引语法启用/修改 CLI 选项。
Git
的实例用于启用/配置 CLI 参数,并有助于 assemble 可以使用的正确编码字符串列表,例如在 subprocess.Popen(...)
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"): ...
观察:
- 正如 @martineau 在评论中指出的那样,CLIOption 装饰器无法访问外部范围。所以范围不能被注释到嵌套的classes.
- 使用嵌套的 classes 是因为 Python 中的一些不错的效果在这里没有演示。还要将它们的范围保持在程序的本地。想象一下,可能有多个程序提供
FlagVersion
标志。有些是 -v
,有些是 --version
。
主要问题:
- 如何检查 class
FlagVersion
是否是 class Git
的嵌套 class?
目前我调查的内容:
- 没有像函数
isinstance(...)
或 issubclass(...)
提供的帮助函数来实现这个目标。
- 虽然 root-level classes 具有对外部范围的
__module__
引用,但嵌套的 classes 没有指向下一个外部范围的“指针”。
- 实际上,嵌套的 class 具有相同的
__module__
值。
有道理。
- A class'
__qualname__
包含 parent classes.
的名字
不幸的是,这是一个像 Git.FlagVersion
这样的字符串
所以我看到一个可能的“丑陋”解决方案,使用 __qualname__
和字符串操作来检查 class 是否嵌套以及它是否嵌套在某个外部范围内。
算法:
- Assemble 来自
__module__
和 __qualname__
的完全限定名称。
- 从左到右逐个元素检查是否匹配。
如果一个嵌套 class 在 parent class 中定义,而另一个嵌套 class 在派生 class 中定义,这将变得更加复杂.然后我还需要研究 MRO ... oOo
次要问题:
- 有没有比使用字符串操作更好的方法?
- 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 包中可用。
我目前正在开发 CLI abstraction layer,它将 CLI 程序抽象为 Python 中的 classes。这样的 CLI 程序提供了一种结构化的方式来启用和配置 CLI 参数。它有助于检查错误的输入并生成正确转义的参数(例如添加双引号)。
注意:下面的例子使用的是Git,而在我的target application中,它将是商业工具,不提供任何PythonAPI 或类似的。
基本思路:
- 工具 Git 的抽象声明了一个
Git
class,它派生自 classProgram
.
这个parentclass实现了所有程序的通用方法 - CLI 选项在
Git
class. 上表示为 嵌套 class 定义
- 嵌套的 classes 标记有 class-based 装饰器
CLIOption
来自Attribute
(有关详细信息,请参阅 https://github.com/pyTooling/pyAttributes) - 可以通过索引语法启用/修改 CLI 选项。
Git
的实例用于启用/配置 CLI 参数,并有助于 assemble 可以使用的正确编码字符串列表,例如在subprocess.Popen(...)
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"): ...
观察:
- 正如 @martineau 在评论中指出的那样,CLIOption 装饰器无法访问外部范围。所以范围不能被注释到嵌套的classes.
- 使用嵌套的 classes 是因为 Python 中的一些不错的效果在这里没有演示。还要将它们的范围保持在程序的本地。想象一下,可能有多个程序提供
FlagVersion
标志。有些是-v
,有些是--version
。
主要问题:
- 如何检查 class
FlagVersion
是否是 classGit
的嵌套 class?
目前我调查的内容:
- 没有像函数
isinstance(...)
或issubclass(...)
提供的帮助函数来实现这个目标。 - 虽然 root-level classes 具有对外部范围的
__module__
引用,但嵌套的 classes 没有指向下一个外部范围的“指针”。 - 实际上,嵌套的 class 具有相同的
__module__
值。
有道理。 - A class'
__qualname__
包含 parent classes.
的名字 不幸的是,这是一个像Git.FlagVersion
这样的字符串
所以我看到一个可能的“丑陋”解决方案,使用 __qualname__
和字符串操作来检查 class 是否嵌套以及它是否嵌套在某个外部范围内。
算法:
- Assemble 来自
__module__
和__qualname__
的完全限定名称。 - 从左到右逐个元素检查是否匹配。
如果一个嵌套 class 在 parent class 中定义,而另一个嵌套 class 在派生 class 中定义,这将变得更加复杂.然后我还需要研究 MRO ... oOo
次要问题:
- 有没有比使用字符串操作更好的方法?
- 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 包中可用。