动态地对一个Enum基类进行子类化 [英] Dynamically subclass an Enum base class

查看:77
本文介绍了动态地对一个Enum基类进行子类化的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I've set up a metaclass and base class pair for creating the line specifications of several different file types I have to parse.

我已经决定使用枚举,因为同一文件中不同行中的许多单独部分通常具有相同的名称。枚举使它们容易分开。另外,规格是刚性的,不需要添加更多的成员,或者稍后再延长线规格。

I have decided to go with using enumerations because many of the individual parts of the different lines in the same file often have the same name. Enums make it easy to tell them apart. Additionally, the specification is rigid and there will be no need to add more members, or extend the line specifications later.

规范类按预期工作。但是,我动态地创建了一些麻烦:

The specification classes work as expected. However, I am having some trouble dynamically creating them:

>>> C1 = LineMakerMeta('C1', (LineMakerBase,), dict(a = 0))
AttributeError: 'dict' object has no attribute '_member_names'

有没有办法呢?下面的例子很好:

Is there a way around this? The example below works just fine:

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


推荐答案

这行:

C1 = enum.EnumMeta('C1', (), dict(a = 0))

失败,完全相同的错误消息。 c code $ c> enumMeta __新的__ 方法需要一个 enum._EnumDict 作为其最后的论点。 _EnumDict dict 的子类,并提供一个名为 _member_names ,当然,一个常规的 dict 没有。当您完成枚举创建的标准机制时,这一切都会在幕后正确发生。这就是为什么你的另一个例子很好。

fails with exactly the same error message. The __new__ method of EnumMeta expects an instance of enum._EnumDict as its last argument. _EnumDict is a subclass of dict and provides an instance variable named _member_names, which of course a regular dict doesn't have. When you go through the standard mechanism of enum creation, this all happens correctly behind the scenes. That's why your other example works just fine.

这行:

C1 = enum.EnumMeta('C1', (), enum._EnumDict())

没有错误。不幸的是,_EnumDict的构造函数被定义为没有参数,所以你不能用你显然想做的关键字来初始化它。

runs with no error. Unfortunately, the constructor of _EnumDict is defined as taking no arguments, so you can't initialize it with keywords as you apparently want to do.

在枚举的实现中backport to Python3.3,以下代码块出现在 EnumMeta 的构造函数中。您可以在LineMakerMeta类中执行类似操作:

In the implementation of enum that's backported to Python3.3, the following block of code appears in the constructor of EnumMeta. You could do something similar in your LineMakerMeta class:

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 必须是一个诚实的上帝 _EnumDict ,我不明白为什么这样做。在任何情况下,执行枚举非常复杂,处理了很多角落。

In the official implementation, in Python3.5, the if statement and the subsequent block of code is gone for some reason. Therefore classdict must be an honest-to-god _EnumDict, and I don't see why this was done. In any case the implementation of Enum is extremely complicated and handles a lot of corner cases.

我意识到这是不是你的问题的一个干涩的答案,但我希望它会指向一个解决方案。

I realize this is not a cut-and-dried answer to your question but I hope it will point you to a solution.

这篇关于动态地对一个Enum基类进行子类化的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆