Python 动态地为枚举基类创建子类

Python 动态地为枚举基类创建子类,python,enums,Python,Enums,我已经设置了一个元类和基类对,用于创建我必须解析的几种不同文件类型的行规范 我决定使用枚举,因为同一文件中不同行的许多单独部分通常具有相同的名称。枚举使区分它们变得容易。此外,规范是严格的,以后无需添加更多构件或扩展线路规范 规范类按预期工作。但是,我在动态创建它们时遇到一些问题: >>> C1 = LineMakerMeta('C1', (LineMakerBase,), dict(a = 0)) AttributeError: 'dict' object has no at

我已经设置了一个元类和基类对,用于创建我必须解析的几种不同文件类型的行规范

我决定使用枚举,因为同一文件中不同行的许多单独部分通常具有相同的名称。枚举使区分它们变得容易。此外,规范是严格的,以后无需添加更多构件或扩展线路规范

规范类按预期工作。但是,我在动态创建它们时遇到一些问题:

>>> C1 = LineMakerMeta('C1', (LineMakerBase,), dict(a = 0))
AttributeError: 'dict' object has no attribute '_member_names'
有办法解决这个问题吗?下面的示例效果很好:

class A1(LineMakerBase):
    Mode = 0, dict(fill=' ', align='>', type='s')
    Level = 8, dict(fill=' ', align='>', type='d')
    Method = 10, dict(fill=' ', align='>', type='d')
    _dummy = 20 # so that Method has a known length

A1.format(**dict(Mode='DESIGN', Level=3, Method=1))
# produces '  DESIGN 3         1'
元类基于
enum.EnumMeta
,如下所示:

import enum

class LineMakerMeta(enum.EnumMeta):
    "Metaclass to produce formattable LineMaker child classes."
    def _iter_format(cls):
        "Iteratively generate formatters for the class members."
        for member in cls:
            yield member.formatter
    def __str__(cls):
        "Returns string line with all default values."
        return cls.format()
    def format(cls, **kwargs):
        "Create formatted version of the line populated by the kwargs members."
        # build resulting string by iterating through members
        result = ''
        for member in cls:
            # determine value to be injected into member
            try:
                try:
                    value = kwargs[member]
                except KeyError:
                    value = kwargs[member.name]
            except KeyError:
                value = member.default
            value_str = member.populate(value)
            result = result + value_str
        return result
基类如下所示:

class LineMakerBase(enum.Enum, metaclass=LineMakerMeta):
    """A base class for creating Enum subclasses used for populating lines of a file.

    Usage:

    class LineMaker(LineMakerBase):
        a = 0,      dict(align='>', fill=' ', type='f'), 3.14
        b = 10,     dict(align='>', fill=' ', type='d'), 1
        b = 15,     dict(align='>', fill=' ', type='s'), 'foo'
        #   ^-start ^---spec dictionary                  ^--default
    """
    def __init__(member, start, spec={}, default=None):
        member.start = start
        member.spec = spec
        if default is not None:
            member.default = default
        else:
            # assume value is numerical for all provided types other than 's' (string)
            default_or_set_type = member.spec.get('type','s')
            default = {'s': ''}.get(default_or_set_type, 0)
            member.default = default
    @property
    def formatter(member):
        """Produces a formatter in form of '{0:<format>}' based on the member.spec
        dictionary. The member.spec dictionary makes use of these keys ONLY (see
        the string.format docs):
            fill align sign width grouping_option precision type"""
        try:
            # get cached value
            return '{{0:{}}}'.format(member._formatter)
        except AttributeError:
            # add width to format spec if not there
            member.spec.setdefault('width', member.length if member.length != 0 else '')
            # build formatter using the available parts in the member.spec dictionary
            # any missing parts will simply not be present in the formatter
            formatter = ''
            for part in 'fill align sign width grouping_option precision type'.split():
                try:
                    spec_value = member.spec[part]
                except KeyError:
                    # missing part
                    continue
                else:
                    # add part
                    sub_formatter = '{!s}'.format(spec_value)
                    formatter = formatter + sub_formatter
            member._formatter = formatter
            return '{{0:{}}}'.format(formatter)
    def populate(member, value=None):
        "Injects the value into the member's formatter and returns the formatted string."
        formatter = member.formatter
        if value is not None:
            value_str = formatter.format(value)
        else:
            value_str = formatter.format(member.default)
        if len(value_str) > len(member) and len(member) != 0:
            raise ValueError(
                    'Length of object string {} ({}) exceeds available'
                    ' field length for {} ({}).'
                    .format(value_str, len(value_str), member.name, len(member)))
        return value_str
    @property
    def length(member):
        return len(member)
    def __len__(member):
        """Returns the length of the member field. The last member has no length.
        Length are based on simple subtraction of starting positions."""
        # get cached value
        try:
            return member._length
        # calculate member length
        except AttributeError:
            # compare by member values because member could be an alias
            members = list(type(member))
            try:
                next_index = next(
                        i+1
                        for i,m in enumerate(type(member))
                        if m.value == member.value
                        )
            except StopIteration:
                raise TypeError(
                       'The member value {} was not located in the {}.'
                       .format(member.value, type(member).__name__)
                       )
            try:
                next_member = members[next_index]
            except IndexError:
                # last member defaults to no length
                length = 0
            else:
                length = next_member.start - member.start
            member._length = length
            return length
类LineMakerBase(enum.enum,元类=LineMakerMeta):
“”“用于创建用于填充文件行的枚举子类的基类。
用法:
类LineMaker(LineMakerBase):
a=0,dict(align='>',fill='',type='f'),3.14
b=10,dict(align='>',fill='',type='d'),1
b=15,dict(align='>',fill='',type='s'),'foo'
#^-start^---spec dictionary^--默认值
"""
def uuu init uuuu(成员,开始,规范={},默认值=无):
member.start=开始
member.spec=spec
如果默认值不是无:
member.default=默认值
其他:
#假设所有提供的类型的值都是数字,而不是“s”(字符串)
默认值\或\集\类型=member.spec.get('type','s'))
default={s':''}.get(默认的\u或\u集\u类型,0)
member.default=默认值
@财产
def格式化程序(成员):
“”“根据member.spec生成格式为“{0:}”的格式化程序
字典。member.spec字典仅使用这些键(请参阅
string.format(文件格式):
填充对齐标志宽度分组\u选项精度类型“”
尝试:
#获取缓存值
返回“{0:{}}”。格式(成员。\u格式化程序)
除属性错误外:
#如果不存在,则将宽度添加到“格式规范”
member.spec.setdefault('width',member.length如果member.length!=0,则为'else')
#使用member.spec字典中的可用部分构建格式化程序
#任何缺少的部分都不会出现在格式化程序中
格式化程序=“”
对于“填充对齐符号宽度分组”选项精度类型“.split()中的零件:
尝试:
等级库值=成员.等级库[零件]
除KeyError外:
#缺失部分
持续
其他:
#添加部分
sub_格式化程序='{!s}'。格式(规格值)
格式化程序=格式化程序+子格式化程序
成员。\格式化程序=格式化程序
返回'{0:{}}'。格式(格式化程序)
def填充(成员,值=无):
“将值注入成员的格式化程序并返回格式化字符串。”
formatter=member.formatter
如果值不是“无”:
value\u str=formatter.format(值)
其他:
value\u str=formatter.format(member.default)
如果len(值)>len(成员)和len(成员)!=0:
升值误差(
'对象字符串{}({})的长度超过了可用长度'
'用于{}({})的字段长度。'
.format(value\u str,len(value\u str),member.name,len(member)))
返回值
@财产
def长度(成员):
返回len(成员)
定义(成员):
“”“返回成员字段的长度。最后一个成员没有长度。
长度基于起始位置的简单减法
#获取缓存值
尝试:
返回成员。\u长度
#计算构件长度
除属性错误外:
#按成员值进行比较,因为成员可以是别名
成员=列表(类型(成员))
尝试:
下一个\u index=下一个(
i+1
对于枚举中的i,m(类型(成员))
如果m.value==member.value
)
除停止迭代外:
提高打字错误(
'成员值{}不在{}中。'
.format(member.value,类型(member)。\u名称\u)
)
尝试:
下一个成员=成员[下一个索引]
除索引器外:
#最后一个成员默认为“无长度”
长度=0
其他:
长度=下一个\u member.start-member.start
成员。_长度=长度
返回长度
此行:

C1 = enum.EnumMeta('C1', (), dict(a = 0))
C1 = enum.EnumMeta('C1', (), enum._EnumDict())
失败,并显示完全相同的错误消息。
EnumMeta
\uuuuu new\uuuuu
方法需要一个
enum.\u EnumDict
的实例作为其最后一个参数
\u EnumDict
dict
的子类,并提供名为
\u member\u names
的实例变量,这当然是常规
dict
没有的。当您使用创建枚举的标准机制时,这一切都会在幕后正确发生。这就是为什么你的另一个例子效果很好

这一行:

C1 = enum.EnumMeta('C1', (), dict(a = 0))
C1 = enum.EnumMeta('C1', (), enum._EnumDict())
运行时没有错误。不幸的是,_EnumDict的构造函数被定义为不带任何参数,因此您不能像显然希望的那样使用关键字初始化它

在后端口到Python3.3的enum实现中,以下代码块出现在
EnumMeta
的构造函数中。您可以在LineMakerMeta类中执行类似的操作:

def __new__(metacls, cls, bases, classdict):
    if type(classdict) is dict:
        original_dict = classdict
        classdict = _EnumDict()
        for k, v in original_dict.items():
            classdict[k] = v
在Python3.5的官方实现中,if语句和随后的代码块由于某种原因消失了。因此,
classdict
必须是
# compare by member values because member could be an alias
members = list(type(member))
# untested
def __new__(metacls, cls, bases, clsdict):
    # let the main EnumMeta code do the heavy lifting
    enum_cls = super(LineMakerMeta, metacls).__new__(cls, bases, clsdict)
    # go through the members and calculate the lengths
    canonical_members = [
           member
           for name, member in enum_cls.__members__.items()
           if name == member.name
           ]
    last_member = None
    for next_member in canonical_members:
        next_member.length = 0
        if last_member is not None:
            last_member.length = next_member.start - last_member.start
>>> from enum import Enum
>>> MyEnum = Enum('MyEnum', {'a': 0})
>>> MyEnum
<enum 'MyEnum'>
>>> MyEnum.a
<MyEnum.a: 0>
>>> type(MyEnum)
<class 'enum.EnumMeta'>