有条件进口的良好做法

Good practice with conditional imports

我有一个配置模块 projectConfig,用于解析项目的示例 sheet:

class SampleSheetFields():
    FIELD_1 = "field1"
    FIELD_2 = "field2"


class SampleSheetFieldsOld():
    FIELD_1 = "field_1"
    FIELD_2 = "field_2"

我一直在像这样的其他模块中使用第一个 class:

from projectConfig import SampleSheetFields as ssFields

class SomeClass

    def __init__(self):
        ...
        check(someContent, ssFields.FIELD_1)

问题是我在开发软件时经常参考 ssFields。在某些时候,新规范表示该软件还应使用具有不同字段名称的示例 sheet。我发现实现该目标的最快方法是在 projectConfig 中添加 class SampleSheetFieldsOld 并在我的模块中进行条件导入:

class SomeClass:

    def __init__(self, useOld):
        if useOld:
            from projectConfig import SampleSheetFieldsOld as ssFields
        else:
            from projectConfig import SampleSheetFields as ssFields

        ...
        check(someContent, ssFields.FIELD_1)

请注意,所使用的必填字段具有相同的名称,因此不存在冲突或缺失的字段。该程序按预期工作。

我的问题是:

  1. 这种做法有多糟糕,如果不好的话;
  2. 我怎样才能绕过它来制作更好、更可持续的代码?

如果您只需要 class 属性,您可以创建一个 class 工厂,使用 type 来创建新的 classes,例如:

FIELDS = dict(
    new=dict(FIELD_1="field1", FIELD_2="field2"),
    old=dict(FIELD_1="field_1", FIELD_2="field_2"),
}


def sample_sheet_field_factory(field_spec='new'):
    return type("SampleSheetFields", (object,), FIELDS[field_spec])

这可以很容易地扩展到更多的字段规范集,并且不需要有条件的导入:

from wherever import sample_sheet_field_factory

class SomeClass(object):

    def __init__(self, use_old):
        ss_fields = sample_sheet_field_factory("old" if use_old else "new")
        check(some_content, ss_fields.FIELD_1)

您也可以使用 namedtuple, rather than a class, which would be a little more lightweight. Note edits for compliance with the style guide

这可能不是最糟糕的事情,但我发现有点问题的是您现在被锁定在两个配置选项中,旧的和新的。如果有一天你需要添加第三个或第四个等等怎么办?您将无法再使用简单的布尔测试。

此外,您的配置选项看起来都只是简单的字符串值,可通过字符串键访问。你不需要 class。

我的建议是忘记使用源代码进行配置并使用配置 文件。在您的 projectConfig 中,您可以拥有一个从文件初始化的 dict,其 path/name 可以在命令行上或以任何方便的方式提供。所以 projectConfig.py 可能是这样的:

config_options = {}

def load_configuration(filename):
    with open(filename) as f:
        for line in f:
            # get key and value
            config_options[key] = value

然后在任何需要获取字段名称的地方,只需访问projectConfig.config_options['field_key'],例如

from projectConfig import config_options

class SomeClass

    def __init__(self):
        ...
        check(someContent, config_options['FIELD_1'])

或者如果有合理的默认值就用dict.get(key, default)。这样,每次需要切换到不同的一组字段名时,您只需创建一个新的配置文件,而不必触摸代码。

Python 的标准库包含一个 configparser module 可以为您处理加载。