Pythonic 方式获取在 yaml 中定义或未定义的值
Pythonic way to get value defined or not defined in yaml
我有一个带有测试配置的 yaml 文件,并且在可选部分 "test-options" 中有一个可选参数 "ignore-dup-txn"。
test-name:
test-type: trh_txn
test-src-format: excel
test-src-excel-sheet: invalid_txns
test-options:
ignore-dup-txn: True
我阅读了 "test-name" 部分到 "test" dict,现在我这样检查:
if 'test-options' in test and 'ignore-dup-txn' in test['test-options']:
ignore_dups = test['test-options']['ignore-dup-txn']
else:
ignore_dups = None
pythonic 的方法是什么?更清晰、更简单、更简短。
我想做 "getter",但如果我做 get(test['test-option']['ignore-dup-txn'])
,显然如果选项没有定义,我会得到一个异常。
您可以使用get
方法:
test['test-options'].get('ignore-dup-txn',
默认值)
这可行:
test.get('test-options', {}).get('ignore-dup-txn', None)
如果你只想要一个 "one-liner" 而不想创建一个空的字典,你可以这样做:
ignore_dups = test['test-options'].get('ignore-dup-txn') if 'test-options' in test else None
但这会导致排长队,并且不能很好地扩展到另一个级别并且不是很 pythonic。
对于 IMO 的东西更 pythonic 首先看看当你有一个 dict
并使用列表作为赋值键或作为 .get()
的第一个参数时会发生什么¹:
d = dict()
l = ['a', 'b', 'c']
try:
d[l] = 3
except TypeError as e:
assert e.message == "unhashable type: 'list'"
else:
raise NotImplementedError
try:
d.get(l, None)
except TypeError as e:
assert e.message == "unhashable type: 'list'"
else:
raise NotImplementedError
这意味着 some_dict.get(['a', 'b', 'c'], default)
将抛出 TypeError。另一方面,这是一种相当简洁的语法,可以从...中的字典中的字典中获取值。
所以问题变成了如何让这样的 .get()
工作?
首先你必须意识到你不能仅仅替换 dict
上的 .get()
方法,你会得到一个 AttributeError
:
d = dict()
def alt_get(key, default):
pass
try:
d.get = alt_get
except AttributeError as e:
assert e.message == "'dict' object attribute 'get' is read-only"
else:
raise NotImplementedError
所以你必须继承 dict
,这允许你覆盖 .get()
方法:
class ExtendedDict(dict):
def multi_level_get(self, key, default=None):
if not isinstance(key, list):
return self.get(key, default)
# assume that the key is a list of recursively accessible dicts
# *** using [] and not .get() in the following on purpose ***
def get_one_level(key_list, level, d):
if level >= len(key_list):
if level > len(key_list):
raise IndexError
return d[key_list[level-1]]
return get_one_level(key_list, level+1, d[key_list[level-1]])
try:
return get_one_level(key, 1, self)
except KeyError:
return default
get = multi_level_get # delete this if you don't want to mask get()
# you can still use the multi_level-get()
d = dict(a=dict(b=dict(c=42)))
assert d['a']['b']['c'] == 42
try:
d['a']['xyz']['c'] == 42
except KeyError as e:
assert e.message == 'xyz'
else:
raise NotImplementedError
ed = ExtendedDict(d)
assert ed['a']['b']['c'] == 42
assert ed.get(['a', 'b', 'c'], 196) == 42
assert ed.get(['a', 'xyz', 'c'], 196) == 196 # no execption!
当只在字典中递归地包含字典时,这很好用,但当你将它们与列表混合时,它也会在有限的范围内工作:
e = dict(a=[dict(c=42)])
assert e['a'][0]['c'] == 42
ee = ExtendedDict(e)
# the following works becauuse get_one_level() uses [] and not get()
assert ee.get(['a', 0, 'c'], 196) == 42
try:
ee.get(['a', 1, 'c'], 196) == 42
except IndexError as e:
assert e.message == 'list index out of range'
else:
raise NotImplementedError
try:
ee.get(['a', 'b', 'c'], 196) == 42
except TypeError as e:
assert e.message == 'list indices must be integers, not str'
else:
raise NotImplementedError
你当然也可以在 multi_level_get()
中捕获后两个错误,方法是使用 except (KeyError, TypeError, IndexError):
并返回默认值
对于所有这些情况。
在 ruamel.yaml ² 这个多级获取实现为 mlget()
(这需要一个额外的参数来允许列表成为层次结构的一部分):
import ruamel.yaml as yaml
from ruamel.yaml.comments import CommentedMap
yaml_str = """\
test-name:
test-type: trh_txn
test-src-format: excel
test-src-excel-sheet: invalid_txns
test-options:
ignore-dup-txn: True
"""
data = yaml.load(yaml_str, Loader=yaml.RoundTripLoader)
assert data['test-name']['test-options']['ignore-dup-txn'] is True
assert data.mlget(['test-name', 'test-options', 'ignore-dup-txn'], 42) is True
assert data.mlget(['test-name', 'test-options', 'abc'], 42) == 42
print(data['test-name']['test-src-format'])
打印:
excel
¹ 在示例中,我宁愿使用断言来确认正在发生的事情,而不是打印语句然后单独解释打印的内容。这使解释更加简洁,并且在 try
/except
块中的断言的情况下清楚地表明异常被抛出,而不会破坏代码并禁止执行后续代码。所有示例代码均来自 python 文件,该文件运行并仅打印一个值。
² 我是那个包的作者,它是PyYAML的增强版。
我有一个带有测试配置的 yaml 文件,并且在可选部分 "test-options" 中有一个可选参数 "ignore-dup-txn"。
test-name:
test-type: trh_txn
test-src-format: excel
test-src-excel-sheet: invalid_txns
test-options:
ignore-dup-txn: True
我阅读了 "test-name" 部分到 "test" dict,现在我这样检查:
if 'test-options' in test and 'ignore-dup-txn' in test['test-options']:
ignore_dups = test['test-options']['ignore-dup-txn']
else:
ignore_dups = None
pythonic 的方法是什么?更清晰、更简单、更简短。
我想做 "getter",但如果我做 get(test['test-option']['ignore-dup-txn'])
,显然如果选项没有定义,我会得到一个异常。
您可以使用get
方法:
test['test-options'].get('ignore-dup-txn',
默认值)
这可行:
test.get('test-options', {}).get('ignore-dup-txn', None)
如果你只想要一个 "one-liner" 而不想创建一个空的字典,你可以这样做:
ignore_dups = test['test-options'].get('ignore-dup-txn') if 'test-options' in test else None
但这会导致排长队,并且不能很好地扩展到另一个级别并且不是很 pythonic。
对于 IMO 的东西更 pythonic 首先看看当你有一个 dict
并使用列表作为赋值键或作为 .get()
的第一个参数时会发生什么¹:
d = dict()
l = ['a', 'b', 'c']
try:
d[l] = 3
except TypeError as e:
assert e.message == "unhashable type: 'list'"
else:
raise NotImplementedError
try:
d.get(l, None)
except TypeError as e:
assert e.message == "unhashable type: 'list'"
else:
raise NotImplementedError
这意味着 some_dict.get(['a', 'b', 'c'], default)
将抛出 TypeError。另一方面,这是一种相当简洁的语法,可以从...中的字典中的字典中获取值。
所以问题变成了如何让这样的 .get()
工作?
首先你必须意识到你不能仅仅替换 dict
上的 .get()
方法,你会得到一个 AttributeError
:
d = dict()
def alt_get(key, default):
pass
try:
d.get = alt_get
except AttributeError as e:
assert e.message == "'dict' object attribute 'get' is read-only"
else:
raise NotImplementedError
所以你必须继承 dict
,这允许你覆盖 .get()
方法:
class ExtendedDict(dict):
def multi_level_get(self, key, default=None):
if not isinstance(key, list):
return self.get(key, default)
# assume that the key is a list of recursively accessible dicts
# *** using [] and not .get() in the following on purpose ***
def get_one_level(key_list, level, d):
if level >= len(key_list):
if level > len(key_list):
raise IndexError
return d[key_list[level-1]]
return get_one_level(key_list, level+1, d[key_list[level-1]])
try:
return get_one_level(key, 1, self)
except KeyError:
return default
get = multi_level_get # delete this if you don't want to mask get()
# you can still use the multi_level-get()
d = dict(a=dict(b=dict(c=42)))
assert d['a']['b']['c'] == 42
try:
d['a']['xyz']['c'] == 42
except KeyError as e:
assert e.message == 'xyz'
else:
raise NotImplementedError
ed = ExtendedDict(d)
assert ed['a']['b']['c'] == 42
assert ed.get(['a', 'b', 'c'], 196) == 42
assert ed.get(['a', 'xyz', 'c'], 196) == 196 # no execption!
当只在字典中递归地包含字典时,这很好用,但当你将它们与列表混合时,它也会在有限的范围内工作:
e = dict(a=[dict(c=42)])
assert e['a'][0]['c'] == 42
ee = ExtendedDict(e)
# the following works becauuse get_one_level() uses [] and not get()
assert ee.get(['a', 0, 'c'], 196) == 42
try:
ee.get(['a', 1, 'c'], 196) == 42
except IndexError as e:
assert e.message == 'list index out of range'
else:
raise NotImplementedError
try:
ee.get(['a', 'b', 'c'], 196) == 42
except TypeError as e:
assert e.message == 'list indices must be integers, not str'
else:
raise NotImplementedError
你当然也可以在 multi_level_get()
中捕获后两个错误,方法是使用 except (KeyError, TypeError, IndexError):
并返回默认值
对于所有这些情况。
在 ruamel.yaml ² 这个多级获取实现为 mlget()
(这需要一个额外的参数来允许列表成为层次结构的一部分):
import ruamel.yaml as yaml
from ruamel.yaml.comments import CommentedMap
yaml_str = """\
test-name:
test-type: trh_txn
test-src-format: excel
test-src-excel-sheet: invalid_txns
test-options:
ignore-dup-txn: True
"""
data = yaml.load(yaml_str, Loader=yaml.RoundTripLoader)
assert data['test-name']['test-options']['ignore-dup-txn'] is True
assert data.mlget(['test-name', 'test-options', 'ignore-dup-txn'], 42) is True
assert data.mlget(['test-name', 'test-options', 'abc'], 42) == 42
print(data['test-name']['test-src-format'])
打印:
excel
¹ 在示例中,我宁愿使用断言来确认正在发生的事情,而不是打印语句然后单独解释打印的内容。这使解释更加简洁,并且在 try
/except
块中的断言的情况下清楚地表明异常被抛出,而不会破坏代码并禁止执行后续代码。所有示例代码均来自 python 文件,该文件运行并仅打印一个值。
² 我是那个包的作者,它是PyYAML的增强版。