有条件进口的良好做法
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)
请注意,所使用的必填字段具有相同的名称,因此不存在冲突或缺失的字段。该程序按预期工作。
我的问题是:
- 这种做法有多糟糕,如果不好的话;
- 我怎样才能绕过它来制作更好、更可持续的代码?
如果您只需要 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 可以为您处理加载。
我有一个配置模块 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)
请注意,所使用的必填字段具有相同的名称,因此不存在冲突或缺失的字段。该程序按预期工作。
我的问题是:
- 这种做法有多糟糕,如果不好的话;
- 我怎样才能绕过它来制作更好、更可持续的代码?
如果您只需要 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 可以为您处理加载。