Dynamically subclass an Enum base class
我已经设置了一个元类和基类对,用于创建必须解析的几种不同文件类型的行规范。
我决定使用枚举,因为同一文件中不同行的许多单独部分通常具有相同的名称。枚举使区分它们变得容易。此外,规范是刚性的,不需要添加更多的成员,也不需要在以后扩展行规范。
规范类按预期工作。但是,我在动态创建它们时遇到一些困难:
1 2 | >>> C1 = LineMakerMeta('C1', (LineMakerBase,), dict(a = 0)) AttributeError: 'dict' object has no attribute '_member_names' |
有办法解决这个问题吗?下面的示例工作得很好:
1 2 3 4 5 6 7 8 | 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' |
号
元类基于
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | 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 |
基本类如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 | 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 |
。
此行:
1 | C1 = enum.EnumMeta('C1', (), dict(a = 0)) |
失败,错误消息完全相同。
此行:
1 | C1 = enum.EnumMeta('C1', (), enum._EnumDict()) |
号
无错误运行。不幸的是,Enumdict的构造函数被定义为不带参数,因此您不能像您显然希望的那样用关键字初始化它。
在返回到python3.3的枚举的实现中,下面的代码块出现在
1 2 3 4 5 6 | 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语句和随后的代码块由于某种原因而消失。所以以东十一〔8〕必须对神以东十一〔3〕诚实,我不明白这是为什么。在任何情况下,
我知道这不是对你问题的一个简单的回答,但我希望它能给你指出一个解决办法。
创建您的
1 | C1 = LineMakerBase('C1', dict(a=0)) |
。
元类不应该像您试图使用它的方式那样使用。查看这个答案以获取何时需要元类子类的建议。
关于您的代码的一些建议:
两次尝试/除了在
1 2 3 4 5 6 7 | for member in cls: if member in kwargs: value = kwargs[member] elif member.name in kwargs: value = kwargs[member.name] else: value = member.default |
此代码:
1 2 | # compare by member values because member could be an alias members = list(type(member)) |
。
现在您没有复杂的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | # 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 |
。
动态创建
1 2 3 4 5 6 7 8 | >>> from enum import Enum >>> MyEnum = Enum('MyEnum', {'a': 0}) >>> MyEnum <enum 'MyEnum'> >>> MyEnum.a <MyEnum.a: 0> >>> type(MyEnum) <class 'enum.EnumMeta'> |
至于您的自定义方法,如果使用常规函数可能会更简单,这正是因为