在yaml中定义或未定义值的Pythonic方法

时间:2015-06-12 18:59:33

标签: python yaml 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

我将“测试名称”部分读作“测试”字典,现在我以这种方式检查:

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']),如果没有定义选项,我会得到一个例外。显然。

3 个答案:

答案 0 :(得分:3)

您可以使用@variable_name方法:

get 默认值 test['test-options'].get('ignore-dup-txn',

答案 1 :(得分:2)

这样可行:

test.get('test-options', {}).get('ignore-dup-txn', None)

答案 2 :(得分:0)

如果你只想要一个“单行”并且不想创建一个空的dict,你可以这样做:

ignore_dups = test['test-options'].get('ignore-dup-txn') if 'test-options' in test else None

但是这会导致长线,并且不能很好地扩展到另一个级别并且不是非常pythonic。

对于IMO更热门的东西,首先看看当你有一个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()工作?

首先,您必须意识到,您不能只更换.get()上的dict方法,而是获得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!

当只在递归中使用dicts中的dicts时,这种方法很好,但当你将这些与列表混合时,这种方法也有限:

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²中,这个多级get实现为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的增强版本。