2017-12-31 65 views
3

Ich weiß, dass Python Enums machen kann (gut so), aber die Art der Enums ist sehr primitiv. Zum Beispiel kann ich dies tun:Klassenvariable als Instanz der umschließenden Klasse

from enum import Enum 

class Color(Enum): 
    BLACK = 'black' 
    WHITE = 'white' 

print(Color.BLACK.value) 

Das ist in Ordnung, aber was ist, wenn ich jede Farbe wünschen einen Namen und einen Hex-Wert haben? Ich habe mehrere Möglichkeiten (wie die Enum-Werte Wörterbücher zu machen), aber ich bevorzuge die Art, wie Java enums macht. In Java dürfen Enums Felder und Methoden wie eine Klasse haben. So normal, wenn nur eine Sprache einfach Aufzählungen wie im obigen Beispiel unterstützt, restrukturieren ich den Code ähnlich dem folgenden:

class Color(object): 
    BLACK = Color('black', '#000') 
    WHITE = Color('white', '#fff') 

    def __init__(self, name, hex): 
    self.name = name 
    self.hex = hex 

print(Color.BLACK.name + ' ' + Color.BLACK.hex) 

Jetzt kann ich mehrere Werte, kundenspezifische Methoden und da jeder auf die verschiedenen Felder nach Namen verweisen Farbe ist ein Objekt. Ich habe das in mehreren Sprachen ohne Problem gemacht, aber Python scheint sich zu beklagen, dass "Name 'Farbe' nicht definiert ist". Kann ich keine Instanz einer Klasse in dieser Klasse erstellen? Meine hacky Lösung ist dies zu tun:

class Color(object): 
    def __init__(self, name, hex): 
    self.name = name 
    self.hex = hex 

Color.BLACK = Color('black', '#000') 
Color.WHITE = Color('white', '#fff') 

print(Color.BLACK.name + ' ' + Color.BLACK.hex) 

Was funktioniert gut. Meine Frage ist, warum sind die Felder innerhalb der Klasse nicht erlaubt? Kann ich etwas hinzufügen oder umstrukturieren, um es zu erlauben? Vielen Dank im Voraus für die Antworten!

+2

Nun Python muss eine 'Color' Klasse aufbauen, mit dem Argument' BLACK = Color ('black', '# 000') '. Aber zu dieser Zeit ist 'Color' noch nicht definiert. Es ist also wie das "* Huhn und das Ei *" - Problem. –

Antwort

9

Aufzählungen unterstützen diese usecase direkt. Die Dokumentation für die Bibliothek deckt diese im Planet example in the examples section:

Wenn __new__() oder __init__() wird der Wert des Aufzählungselement definiert wird auf diese Methoden übergeben werden:

>>> class Planet(Enum): 
...  MERCURY = (3.303e+23, 2.4397e6) 
...  # ... 
... 
...  def __init__(self, mass, radius): 
...   self.mass = mass  # in kilograms 
...   self.radius = radius # in meters 
...  @property 
...  def surface_gravity(self): 
...   # universal gravitational constant (m3 kg-1 s-2) 
...   G = 6.67300E-11 
...   return G * self.mass/(self.radius * self.radius) 

[...]

>>> Planet.EARTH.value 
(5.976e+24, 6378140.0) 
>>> Planet.EARTH.surface_gravity 
9.802652743337129 

Also für Ihr spezifisches Beispiel definieren nur eine __init__ Methode:

from enum import Enum 

class Color(Enum): 
    BLACK = ('black', '#000') 
    WHITE = ('white', '#fff') 

    def __init__(self, color_name, hex): 
     self.color_name = color_name 
     self.hex = hex 

print(Color.BLACK.color_name + ' ' + Color.BLACK.hex) 

ich ein Attribut nicht name wie denn das ist ein reserviertes Attribut (verwendet wird, verwendet habe den ENUM-Wert widerzuzuspiegeln nennen, hier BLACK und WHITE):

>>> Color.BLACK 
<Color.BLACK: ('black', '#000')> 
>>> Color.BLACK.name 
'BLACK' 
>>> Color.BLACK.color_name 
'black' 
>>> Color.BLACK.hex 
'#000' 

Sie können das Attribut name mit einem @property überschreiben, aber ich würde hier nicht vom Standard abweichen.

Ich habe diese Technik verwendet, um die Viruszustände in meinem Advent of Code day 22 solution zu definieren, wobei ich sowohl den nächsten Status als auch die Richtungsänderung für jeden Eintrag festlege.

5

Verwenden Tupel Werte und @property Accessoren Namen an die Tupelelemente zu geben:

from enum import Enum 

class Color(Enum): 
    BLACK = ('black', '#000') 
    WHITE = ('white', '#fff') 

    @property 
    def name(self): 
     return self.value[0] 

    @property 
    def hex(self): 
     return self.value[1] 

print(Color.BLACK.name) 
print(Color.BLACK.hex) 

Output:

black 
#000 

Was, warum Ihr Code funktioniert nicht, Python Klassendefinitionen zwingend notwendig sind. Zu dem Zeitpunkt, zu dem Sie versuchen, eine Color Instanz zu erstellen, existiert die Klasse noch nicht.

3

Das "Huhn und Ei "-Problem

Sie stoßen auf das" Huhn und Ei" -Problem hier. Wenn Sie eine Klasse konstruieren, muss Python die Namen von Attributen und Funktionen mit den Werten der Attribute und Funktionen verknüpfen. Wenn Sie auf Color.abc zugreifen, wird gesucht, ob ein entsprechender Name gefunden wird, und die Wert/Funktionsdefinition zurückgegeben.

Aber jetzt gibt es ein Problem. Wenn Sie schreiben:

class Foo(object): 
    bar = Foo()

Warum? Um die class zu konstruieren, müssen zuerst die Attribute konstruiert werden. Es muss also ein 'bar' Eintrag erstellt werden, der dem Ergebnis von Foo() entspricht, aber wir bauen Foo in diesem Moment, also wie können wir einen Foo konstruieren, wenn Foo von dieser Konstruktion abhängt. Wir können nicht. In Java ist es einfacher, da Klassen konzeptionell unter Kompilierungszeit konstruiert werden.

Wir haben hier jedoch einige Optionen.

Monkey-Patchen des Color Klasse

Wir die Color Klasse zuerst konstruieren und dann "Affe Patch" die Klasse:

class Color(object): 

    def __init__(self, name, hex): 
    self.name = name 
    self.hex = hex 

Color.black = Color('black', '#000') Color.white = Color('white', '#fff')

Hier zuerst definieren wir die Color Klasse, und fügen Sie dann Attribute zur Color Klasse. Das können wir später machen, da jetzt die Objekte definiert sind.

Anbringen Werte an die Enum Objekte

Wir können auch Werte heften sich an die Enum Objekte:

from enum import Enum 

class Color(Enum): 

    white = {'name': 'white', 'hex': '#fff'} black = {'name': 'black', 'hex': '#000'} @property def name(self): return self.value['name'] @property def hex(self): return self.value['hex']

Wir können einen Wert für jeden Enum Mitglied befestigen. Zum Beispiel verbinden wir {'name': 'white', 'hex': '#fff'} mit white. Wir können später auf diesen Wert über self.value zugreifen. Nun können wir eine Eigenschaft auf Color.white definieren, indem wir eine Eigenschaftsfunktion def name(self): definieren, die auf den Schlüssel 'name' des Wörterbuchs zugreift.

+0

Klassen sind keine Wörterbücher. Nur Wörterbücher sind Wörterbücher. Es wäre genauer zu sagen, dass Klassen ein Diktat haben oder (wenn Sie das Diktat mehr betonen möchten), dass Klassen Wrappers um Dicts sind. – user2357112

0

Ich denke, user2357112 hat bereits die Antwort, die Sie suchen, aber es könnte sich lohnen, in namedtuples auch auf Eigenschaften zuzugreifen.

Named Tupeln:

from collections import namedtuple 
Color = namedtuple('Color', 'name hex') 
Black = Color(name='Black', hex='#000') 

print(Black.hex) 
print(Black.name) 
>#000 
>Black 
2

Wie wärs mit einem Namen Tupel Lösung?

from collections import namedtuple 

color = namedtuple('Color', ['name', 'value']) # Add attributes as you please 

class Color: 
    BLACK = color('black', '#000') 
    WHITE = color('white', '#fff') 

print(Color.BLACK.name, Color.BLACK.value) 

Ausgänge

schwarz # 000

neue hinzuzufügen ist so einfach wie diese

Color.RED = color('red', '#ff0') 
print(Color.RED.name, Color.RED.value) 

red # ff0

+0

Ich denke, zwei Farbklassen wie diese zu haben, könnte etwas verwirrend sein ('color' ist eine Unterklasse von' tuple', was die 'namedtuple() 'factory Funktion zurückgibt). – martineau

1

Sie können tun, was Sie wollen durch eine Meta-Klasse mit der Color Klasse helfen zu konstruieren:

class ColorMeta(type): 
    def __new__(cls, class_name, parents, attrs): 
     NUL = object() # Sentinel. 
     meta_args = attrs.get('meta_args', NUL) 
     if meta_args is NUL: 
      meta_args = [] 
     else: 
      del attrs['meta_args'] # Clean up so isn't part of class created. 

     clsobj = super().__new__(cls, class_name, parents, attrs) 

     for meta_arg in meta_args: 
      name, hex = meta_arg 
      color = clsobj(name, hex) 
      setattr(clsobj, name, color) 

     return clsobj 


class Color(metaclass=ColorMeta): 
    meta_args = [('WHITE', '#fff'), 
       ('BLACK', '#000'),] 

    def __init__(self, name, hex): 
     self.name = name 
     self.hex = hex 


print(Color.WHITE.name + ' ' + Color.WHITE.hex) 
print(Color.BLACK.name + ' ' + Color.BLACK.hex) 
Verwandte Themen