Python 3.6从配置文件引用Enum Flag

时间:2018-04-02 18:49:42

标签: python python-3.x

使用Python 3.6&枚举( 注意枚举是v3.6的新增功能

我试图弄清楚是否有一个" automagic"通过字符串引用字典的Enum Flags的赋值方式。在我的例子中,一个JSON配置文件加载了指定Enum Flags的文本,但我必须执行一堆" if"将节点重新分配给实际枚举的语句。

下面的示例可以正常工作,但是当您对udpate有很多引用时,它会变得很糟糕。

配置文件较大,包含文本,数字和枚举。

示例代码

#import json
from enum import Enum, Flag

class stream_data(Flag):
    wind=  (0x00000001)
    humidity = (0x00000002)

#with open('strings.json') as json_file:
#    config = json.load(json_file)
# Assume we loaded with json.load
config = {
    "options": {
        "stream": {
            "capture_rate": "1", "stream_data": "wind", "stream_pull_rate": 5, "stream_type": "binary"
        }
    }
}
print('type before: %s' % type(config['options']['stream']['stream_data']))

if config['options']['stream']['stream_data'] == stream_data.wind.name:
        config['options']['stream']['stream_data'] = stream_data.wind

print('type after: %s' % type(config['options']['stream']['stream_data']))

结果:

type before: <class 'str'>
type after: <enum 'stream_data'>

有什么蟒蛇魔法,我不知道吗?

(我以为我可以遍历dict和enum类来查看名称是否匹配,但这看起来有点像kludgy。)

1 个答案:

答案 0 :(得分:2)

枚举会公开其内部dict,以便您可以使用以下命令按名称访问其值:

stream_data["wind"]  # 1
stream_data["humidity"]  # 2
# etc.

因此,在您设置configc以适合而不滚动)的情况下,您可以自动替换为:

c['options']['stream']['stream_data'] = stream_data[c['options']['stream']['stream_data']]

UPDATE :如果要自动化所有内容,可以编写一个小的递归函数来遍历您的结构,并尝试替换与特定Enum名称匹配的所有键的值。全局范围,例如:

import collections
import enum

def decode_enums(target, scope=None):  # a simple recursive enum value replacer
    if isinstance(target, collections.MutableMapping):  # check if dict-like...
        scope = scope or globals()  # if a scope dict is not passed, use globals()
        for k, v in target.items():  # iterate over all the items in the passed dictionary
            if k in scope and issubclass(scope[k], enum.Enum):  # enum found in the scope
                try:
                    target[k] = scope[k][v]  # replace with the 'enum' value
                    continue
                except KeyError:  # the value is not enumerated, fall-back to decoding
                    pass
            target[k] = decode_enums(v, scope)  # dig deeper...
    elif hasattr(target, "__iter__") and not isinstance(target, (str, bytes, bytearray)):
        for v in target:  # iterate over the elements of this iterable
            decode_enums(v)  # try to decode their values
    return target  # return the passed value to enable recursive assignment

现在,您可以按照以下内容定义您的心灵内容:

import enum

class stream_data(enum.Flag):
    wind = 0x00000001
    humidity = 0x00000002

class stream_type(enum.Flag):  # just for the kick of it
    binary = 0x00000001
    text = 0x00000002

然后使用实际的Enum值自动更新您的结构:

def value_info(prefix, name, value):  # a small function to trace the value info
    type_ = type(value)
    print(f'{prefix:<6} - {name}: {value:>30}')
    print(f'{prefix:<6} - type({name}): {type_!s:>24}')

config = {
    "options": {
        "stream": {
            "capture_rate": "1", "stream_data": "wind",
            "stream_pull_rate": 5, "stream_type": "binary"
        }
    }
}

value_info("before", "stream_data", config["options"]["stream"]["stream_data"])
value_info("before", "stream_type", config["options"]["stream"]["stream_type"])

decode_enums(config)

value_info("after", "stream_data", config["options"]["stream"]["stream_data"])
value_info("after", "stream_type", config["options"]["stream"]["stream_type"])

哪个会给你:

before - stream_data:                           wind
before - type(stream_data):            <class 'str'>
before - stream_type:                         binary
before - type(stream_type):            <class 'str'>
after  - stream_data:               stream_data.wind
after  - type(stream_data):     <enum 'stream_data'>
after  - stream_type:             stream_type.binary
after  - type(stream_type):     <enum 'stream_type'>

您甚至可以通过为您的枚举定义特定地图来取消globals()的范围:

class StreamData(enum.Flag):
    wind = 0x00000001
    humidity = 0x00000002

value_info("before", "stream_data", config["options"]["stream"]["stream_data"])
value_info("before", "stream_type", config["options"]["stream"]["stream_type"])

decode_enums(config, {"stream_data": StreamData})

value_info("after", "stream_data", config["options"]["stream"]["stream_data"])
value_info("after", "stream_type", config["options"]["stream"]["stream_type"])

将产生:

before - stream_data:                           wind
before - type(stream_data):            <class 'str'>
before - stream_type:                         binary
before - type(stream_type):            <class 'str'>
after  - stream_data:                StreamData.wind
after  - type(stream_data):      <enum 'StreamData'>
after  - stream_type:                         binary
after  - type(stream_type):            <class 'str'>

您还可以创建一个对应的encode_enums()函数,以便在存储将执行相同递归遍历的JSON时使用,但不是将枚举与其值相关联,而是将值转回名称。

所有这一切都说,鉴于您丢失了重要的类型信息,我建议您转移到YAML format。它允许您创建类型扩展,以便您可以将枚举元数据保留在YAML结构中,并且您不必在解析后遍历整个结构,只是为了尝试猜测枚举值。