Dynamisch Unterklasse eine Enum Basisklasse

Ich habe ein Metaklass- und Basisklassenpaar für die Erstellung der Zeilenspezifikationen von mehreren verschiedenen Dateitypen eingerichtet, die ich analysieren muss.

Ich habe beschlossen, mit der Verwendung von Aufzählungen zu gehen, weil viele der einzelnen Teile der verschiedenen Zeilen in der gleichen Datei oft den gleichen Namen haben. Enums machen es leicht, sie auseinander zu bringen. Darüber hinaus ist die Spezifikation starr und es wird keine Notwendigkeit, weitere Mitglieder hinzuzufügen, oder verlängern die Zeile Spezifikationen später.

Die Spezifikationsklassen arbeiten wie erwartet. Allerdings habe ich einige Schwierigkeiten, sie dynamisch zu erschaffen:

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

Gibt es einen Weg um das? Das folgende Beispiel funktioniert ganz gut:

 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' 

Die enum.EnumMeta basiert auf enum.EnumMeta und sieht so aus:

 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 

Und die Basisklasse ist wie folgt:

 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 

2 Solutions collect form web for “Dynamisch Unterklasse eine Enum Basisklasse”

Diese Linie:

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

Schlägt mit genau der gleichen Fehlermeldung aus. Die __new__ Methode von EnumMeta erwartet eine Instanz von enum._EnumDict als letztes Argument. _EnumDict ist eine Unterklasse von dict und stellt eine Instanzvariable namens _member_names , was natürlich ein regulärer dict nicht hat. Wenn du durch den Standardmechanismus der Enum-Schöpfung gehst, passiert alles alles hinter den Kulissen. Das ist der Grund, warum dein anderes Beispiel gut funktioniert.

Diese Linie:

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

Läuft ohne Fehler. Leider ist der Konstruktor von _EnumDict definiert als keine Argumente, so dass man es nicht mit Schlüsselwörtern initialisieren kann, wie man es scheinbar machen will.

Bei der Implementierung von enum, das in Python3.3 zurückgesetzt wird, erscheint der folgende Codeblock im Konstruktor von EnumMeta . Sie können in Ihrer LineMakerMeta-Klasse etwas Ähnliches machen:

 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 

In der offiziellen Implementierung, in Python3.5, ist die if-Anweisung und der nachfolgende Codeblock aus irgendeinem Grund weg. Darum muss der classdict ein ehrlicher Gott _EnumDict , und ich sehe nicht, warum dies geschehen ist. In jedem Fall ist die Implementierung von Enum äußerst kompliziert und behandelt viele Eckfälle.

Ich weiß, das ist nicht eine Schnitt-und-getrocknete Antwort auf Ihre Frage, aber ich hoffe, es wird Sie auf eine Lösung hinweisen.

Erstellen Sie Ihre LineMakerBase Klasse und verwenden Sie sie dann so:

 C1 = LineMakerBase('C1', dict(a=0)) 

Die Metaklasse war nicht dazu bestimmt, die Art und Weise zu benutzen, wie du es versuchst, es zu benutzen. Überprüfen Sie diese Antwort für Ratschläge, wenn metaclass Unterklassen benötigt werden.


Einige Vorschläge für Ihren Code:

Der doppelte Versuch / außer im format scheint klarer als:

  for member in cls: if member in kwargs: value = kwargs[member] elif member.name in kwargs: value = kwargs[member.name] else: value = member.default 

Dieser Code:

 # compare by member values because member could be an alias members = list(type(member)) 
  1. list(member.__class__) klarer mit der list(member.__class__)
  2. EnumMeta einen falschen Kommentar: list eine Enum Klasse wird nie die Aliase (es sei denn, Sie haben diesen Teil von EnumMeta )

Anstelle des komplizierten __len__ hast du jetzt, und solange du Unterklassen EnumMeta bist, __new__ du EnumMeta verlängern, um die Längen einmal automatisch zu berechnen:

 # 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 
  • Ist es möglich, eine Klassenkonstante innerhalb eines Enum zu definieren?
  • Ist es möglich, __new__ in einem enum zu überschreiben, um Strings zu einer Instanz zu analysieren?
  • Verwenden des Python-Enum-Moduls für Ctypes
  • Wie kann man ENUM in SQLAlchemy erstellen?
  • Wie soll ich enum in Python am besten emulieren und / oder vermeiden?
  • Können Namensargumente mit Python-Enums verwendet werden?
  • Wie kann ich Enums in Python vergleichen?
  • Python enum - immer Wert von enum auf String Umwandlung
  • Zukunftssichere Enums in 2.7?
  • Prüfung auf gültige Enum-Typen von Protobufs
  • Serialisierung eines Enum-Mitglieds zu JSON
  • Python ist die beste Programmiersprache der Welt.