我已经设置了一个元类和基类对,用于创建我必须解析的几种不同文件类型的行规范。
我决定使用枚举,因为同一文件中不同行的许多单独部分通常具有相同的名称。枚举可以很容易地区分它们。此外,规范是严格的,不需要添加更多成员,或稍后扩展行规范。
规范类按预期工作。但是,我在动态创建它时遇到了一些麻烦:
>>> 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
答案 0 :(得分:2)
这一行:
C1 = enum.EnumMeta('C1', (), dict(a = 0))
失败并出现完全相同的错误消息。 __new__
的{{1}}方法期望EnumMeta
的实例作为其最后一个参数。 enum._EnumDict
是_EnumDict
的子类,并提供名为dict
的实例变量,当然常规_member_names
没有。当你通过枚举创建的标准机制时,这一切都在幕后正确发生。这就是为什么你的另一个例子工作正常。
这一行:
dict
运行没有错误。不幸的是,_EnumDict的构造函数被定义为不带参数,所以你不能用你想要做的关键字来初始化它。
在向后移植到Python3.3的枚举的实现中,以下代码块出现在C1 = enum.EnumMeta('C1', (), enum._EnumDict())
的构造函数中。你可以在LineMakerMeta类中做类似的事情:
EnumMeta
在官方实现中,在Python3.5中,if语句和后续代码块由于某种原因而消失。因此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
必须是诚实的上帝classdict
,我不明白为什么这样做。在任何情况下,_EnumDict
的实现都非常复杂,并处理了许多极端情况。
我意识到这不是对你的问题的简洁回答,但我希望它会指出你的解决方案。
答案 1 :(得分:1)
创建您的LineMakerBase
类,然后像这样使用它:
C1 = LineMakerBase('C1', dict(a=0))
元类并不意味着以您尝试使用它的方式使用。查看this answer以获取有关何时需要元类子类的建议。
您的代码的一些建议:
format
中的double try / except似乎更清晰:
for member in cls:
if member in kwargs:
value = kwargs[member]
elif member.name in kwargs:
value = kwargs[member.name]
else:
value = member.default
此代码:
# compare by member values because member could be an alias
members = list(type(member))
list(member.__class__)
会更清楚list
Enum
类将从不包含别名(除非您已覆盖EnumMeta
的那部分)而不是您现在拥有的复杂__len__
代码,只要您是EnumMeta
的子类,就应该扩展__new__
以自动计算一次长度:
# 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
答案 2 :(得分:0)
动态创建Enum
子类的最简单方法是使用Enum
itself:
>>> from enum import Enum
>>> MyEnum = Enum('MyEnum', {'a': 0})
>>> MyEnum
<enum 'MyEnum'>
>>> MyEnum.a
<MyEnum.a: 0>
>>> type(MyEnum)
<class 'enum.EnumMeta'>
至于你的自定义方法,如果使用常规函数可能会更简单,因为Enum
实现非常特殊。