将命令行输出解析为容器对象的 Pythonic 方法

Pythonic way to parse command line output into a container object

请在回答之前阅读整个问题,因为这不是您的想法...我正在研究创建 python 表示系统上硬件设备的对象包装器(下面的修剪示例)。

class TPM(object):

    @property
    def attr1(self):
        """
        Protects value from being accidentally modified after 
        constructor is called.
        """
        return self._attr1

    def __init__(self, attr1, ...):
        self._attr1 = attr1
        ...

    @classmethod
    def scan(cls):
        """Calls Popen, parses to dict, and passes **dict to constructor"""

大多数构造函数输入涉及 运行 在 subprocess.Popen 中的命令行输出,然后解析输出以填充对象属性。我已经想出了一些方法来处理这些问题,但我对我目前所做的一切并不满意,我正在努力寻找更好的解决方案。以下是我发现的常见问题。 (快速说明:工具版本受到严格控制,因此解析后的输出不会意外更改。)

  1. 许多工具会产生不同的输出,有时包括字段,有时不包括。这意味着如果你 assemble 一个 dict 被包装在一个容器对象中,构造函数或多或少被迫接受 **kwargs 而不是真正定义的字段。我不喜欢这个,因为它使通过 pylint 等进行的静态分析变得毫无用处。我更喜欢定义的接口,以便 sphinx 文档更清晰并且可以更可靠地检测到错误。

  2. 代替**kwargs,我还尝试将许多字段的默认参数设置为None,结果非常难看。我非常不喜欢这个选项的一件事是可选字段并不总是出现在命令行工具输出的末尾。这使得查看构造函数并将其与工具输出相匹配有点令人费解。

  3. 我非常希望首先避免构建字典,但是使用 setattr 创建属性将使 pylint 无法检测到 _attr1 等...并创建警告。欢迎任何想法...

基本上,我正在寻找合适的 Pythonic 方法来做到这一点。我的要求,重新总结如下:

  1. 命令行工具输出解析为容器对象。
  2. 容器对象通过属性 post-构造来保护属性。
  3. 构造函数的输入数量不同,在运行时对缺少的必填字段进行工作静态分析和错误检测。

在 Python 中有没有好的方法(希望没有大量样板代码)?如果有,那是什么?


编辑:

根据一些澄清请求,我们可以查看 tpm_version 命令。这是我的笔记本电脑的输出,但对于此 TPM,它不包括所有可能的属性。有时,该命令会 return 我也想捕获的额外属性。这使得解析容器对象上的已知属性名称相当困难。

TPM 1.2 Version Info:
Chip Version:        1.2.4.40
Spec Level:          2
Errata Revision:     3
TPM Vendor ID:       IFX
Vendor Specific data: 04280077 0074706d 3631ffff ff
TPM Version:         01010000
Manufacturer Info:   49465800

示例代码(请忽略缺少健全性检查。为简洁起见进行了修剪):

def __init__(self, chip_version, spec_level, errata_revision,
             tpm_vendor_id, vendor_specific_data, tpm_version,
             manufacturer_info):
    self._chip_version = chip_version
    ...

@classmethod
def scan(cls):
    tpm_proc = Popen("/usr/sbin/tpm_version")
    stdout, stderr = Popen.communicate()

    tpm_dict = dict()
    for line in tpm_proc.stdout.splitlines():
        if "Version Info:" in line:
            pass
        else:
            split_line = line.split(":")
            attribute_name = (
                split_line[0].strip().replace(' ', '_').lower())
            tpm_dict[attribute_name] = split_line[1].strip()
    return cls(**tpm_dict)

这里的问题是这个(或另一个我可能无法查看来源以获取每个可能的字段的不同的)可能会添加额外的东西导致我的解析器工作,但我的对象无法捕获田野。这就是我真正想以一种优雅的方式解决的问题。

在过去的几个月里,我一直在努力寻找更可靠的答案,因为我基本上是在硬件支持库上工作,并最终得出了一个令人满意的(虽然相当冗长)答案。

  1. 将工具输出(无论它们看起来像什么)解析为与 工具 查看设备的方式相匹配的对象结构。这些可以有非常通用的dict结构,但应该尽可能分解。
  2. 在使用属性访问 tool-container-objects 中的项目的容器之上创建另一个容器 class。这会在工具的多个版本和不同的工具输出中强制执行 API 并且 return 合理的错误!