2013-10-22 4 views
11

Ich bin neugierig auf eine gute Möglichkeit, Wertobjekt in Python zu definieren. Laut Wikipedia: "value object ist ein kleines Objekt, das eine einfache Entität darstellt, deren Gleichheit nicht auf Identität basiert: d. H. Zwei Wertobjekte sind gleich, wenn sie denselben Wert haben und nicht notwendigerweise dasselbe Objekt sind". In Python bedeutet das im Wesentlichen neu definiert __eq__ und __hash__ Methoden, sowie Unveränderlichkeit.Wie definiert man ein PyCharm-freundliches Wertobjekt in Python?

Standard namedtuple scheint fast perfekte Lösung mit der Ausnahme, dass sie nicht gut mit modernen Python IDE wie PyCharm spielen. Ich meine, dass die IDE keine hilfreichen Erkenntnisse über die Klasse namedtuple liefert. Während es möglich ist docstring zu dieser Klasse mit Trick so befestigen:

class Point2D(namedtuple("Point2D", "x y")): 
    """Class for immutable value objects""" 
    pass 

es gibt einfach keinen Ort, wo Beschreibung der Konstruktor Argumente setzen und ihre Typen angeben. PyCharm ist schlau genug, Argumente für Point2D "Konstruktor" zu erraten, aber typenweise ist es blind.

Dieser Code hat irgendeine Art Informationen in gedrückt, aber es ist nicht sehr nützlich:

class Point2D(namedtuple("Point2D", "x y")): 
    """Class for immutable value objects""" 
    def __new__(cls, x, y): 
     """ 
     :param x: X coordinate 
     :type x: float 

     :param y: Y coordinate 
     :type y: float 

     :rtype: Point2D 
     """ 
     return super(Point2D, cls).__new__(cls, x, y) 

point = Point2D(1.0, 2.0) 

PyCharm Typen sehen, wenn neue Objekte konstruieren, wird aber nicht, dass point.x und point.y greift Schwimmer ist, also würde nicht helfen, ihren Missbrauch zu erkennen. Und ich mag auch nicht die Idee, "magische" Methoden routinemäßig neu zu definieren.

Ich suche nach etwas, so dass sein:

  • genauso einfach wie normale Python-Klasse oder namedtuple
  • bieten Wert Semantik (Gleichheit, Hashes, Unveränderlichkeit)
  • leicht zu definieren Dokument in einer Weise, die gut mit IDE

Ideale Lösung könnte so aussehen wie zu spielen:

class Point2D(ValueObject): 
    """Class for immutable value objects""" 
    def __init__(self, x, y): 
     """ 
     :param x: X coordinate 
     :type x: float 

     :param y: Y coordinate 
     :type y: float 
     """ 
     super(Point2D, self).__init__(cls, x, y) 

Oder dass:

class Point2D(object): 
    """Class for immutable value objects""" 

    __metaclass__ = ValueObject 

    def __init__(self, x, y): 
     """ 
     :param x: X coordinate 
     :type x: float 

     :param y: Y coordinate 
     :type y: float 
     """ 
     pass 

Ich habe versucht, etwas zu finden, aber ohne Erfolg. Ich dachte, dass es ratsam ist, um Hilfe zu bitten, bevor ich es selbst ausführe.

UPDATE: Mit Hilfe von user4815162342 gelang es mir, etwas zu finden, das funktioniert. Hier ist der Code:

class ValueObject(object): 
    __slots__ =() 

    def __repr__(self): 
     attrs = ' '.join('%s=%r' % (slot, getattr(self, slot)) for slot in self.__slots__) 
     return '<%s %s>' % (type(self).__name__, attrs) 

    def _vals(self): 
     return tuple(getattr(self, slot) for slot in self.__slots__) 

    def __eq__(self, other): 
     if not isinstance(other, ValueObject): 
      return NotImplemented 
     return self.__slots__ == other.__slots__ and self._vals() == other._vals() 

    def __ne__(self, other): 
     return not self == other 

    def __hash__(self): 
     return hash(self._vals()) 

    def __getstate__(self): 
     """ 
     Required to pickle classes with __slots__ 
     Must be consistent with __setstate__ 
     """ 
     return self._vals() 

    def __setstate__(self, state): 
     """ 
     Required to unpickle classes with __slots__ 
     Must be consistent with __getstate__ 
     """ 
     for slot, value in zip(self.__slots__, state): 
      setattr(self, slot, value) 

Es ist sehr weit von einer idealen Lösung entfernt. Klassendeklaration sieht wie folgt aus:

class X(ValueObject): 
    __slots__ = "a", "b", "c" 

    def __init__(self, a, b, c): 
     """ 
     :param a: 
     :type a: int 
     :param b: 
     :type b: str 
     :param c: 
     :type c: unicode 
     """ 
     self.a = a 
     self.b = b 
     self.c = c 

Es ist insgesamt viermal alle Attribute zur Liste: in __slots__, in Ctor Argumente, in docstring und in Ctor Körper. Bisher habe ich keine Ahnung, wie ich es weniger peinlich machen soll.

+1

Beachten Sie, dass 'namedtuple' in erster Linie sowohl die Tupel-Schnittstelle (Indexierung, Entpacken) als auch den Zugriff auf Attribute bereitstellt. Es wurde für die Abwärtskompatibilität von Funktionen erfunden, die zum Zurückgeben von Tupeln wie "os.stat" oder "time.gmtime" verwendet wurden. Es ist wahrscheinlich nicht die optimale Wahl für einen einfachen Werttyp. – user4815162342

+0

In Bezug auf Typen: * PyCharm ist schlau genug, Argumente für Point2D "Konstruktor" zu erraten, aber typenweise ist es blind * Vielleicht sollten Sie eine statisch typisierte Sprache verwenden? In Python sollte es nicht so eine große Sache sein, dass eine IDE über Typen blind ist. – user4815162342

+0

Nun, 'namedtuple' machen fast den richtigen Job für mich. Es ist definitiv mehr als ein einfaches Wertobjekt, aber ich kann damit leben. Wie für die Verwendung statisch getippter Sprache, ich wünschte, ich könnte. Aber ich habe ein Python-Projekt an der Hand und suche nach einer Möglichkeit, die Entwicklung komfortabler zu gestalten. Und PyCharm macht schon sehr gute Arbeit, um den Typ der Variablen mit Hilfe von Docstrings zu ermitteln. –

Antwort

3

Ihre Anforderungen sind, obwohl sie sorgfältig formuliert sind, für mich nicht ganz klar, teilweise weil ich die PyCharm GUI nicht verwende.Aber hier ist ein Versuch:

class ValueObject(object): 
    __slots__ =() 

    def __init__(self, *vals): 
     if len(vals) != len(self.__slots__): 
      raise TypeError, "%s.__init__ accepts %d arguments, got %d" \ 
       % (type(self).__name__, len(self.__slots__), len(vals)) 
     for slot, val in zip(self.__slots__, vals): 
      super(ValueObject, self).__setattr__(slot, val) 

    def __repr__(self): 
     return ('<%s[0x%x] %s>' 
       % (type(self).__name__, id(self), 
        ' '.join('%s=%r' % (slot, getattr(self, slot)) 
          for slot in self.__slots__))) 

    def _vals(self): 
     return tuple(getattr(self, slot) for slot in self.__slots__) 

    def __eq__(self, other): 
     if not isinstance(other, ValueObject): 
      return NotImplemented 
     return self.__slots__ == other.__slots__ and self._vals() == other._vals() 

    def __ne__(self, other): 
     return not self == other 

    def __hash__(self): 
     return hash(self._vals()) 

    def __setattr__(self, attr, val): 
     if attr in self.__slots__: 
      raise AttributeError, "%s slot '%s' is read-only" % (type(self).__name__, attr) 
     super(ValueObject, self).__setattr__(attr, val) 

Verwendung ist wie folgt:

class X(ValueObject): 
    __slots__ = 'a', 'b' 

Diese bekommt man einen konkreten Wert Klasse mit zwei Nur-Lese-Slots und einen automatisch generierten Konstruktor, __eq__ und __hash__. Zum Beispiel:

>>> x = X(1.0, 2.0, 3.0) 
Traceback (most recent call last): 
    File "<input>", line 1, in <module> 
    File "<input>", line 5, in __init__ 
TypeError: X.__init__ accepts 2 arguments, got 3 
>>> x = X(1.0, 2.0) 
>>> x 
<X[0x4440a50] a=1.0 b=2.0> 
>>> x.a 
1.0 
>>> x.b 
2.0 
>>> x.a = 10 
Traceback (most recent call last): 
    File "<input>", line 1, in <module> 
    File "<input>", line 32, in __setattr__ 
AttributeError: X slot 'a' is read-only 
>>> x.c = 10 
Traceback (most recent call last): 
    File "<input>", line 1, in <module> 
    File "<input>", line 33, in __setattr__ 
AttributeError: 'X' object has no attribute 'c' 
>>> dir(x) 
['__class__', '__delattr__', '__dict__', '__doc__', '__eq__', '__format__', '__getattribute__', '__hash__', '__init__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__slots__', '__str__', '__subclasshook__', '__weakref__', '_vals', 'a', 'b'] 
>>> x == X(1.0, 2.0) 
True 
>>> x == X(1.0, 3.0) 
False 
>>> hash(x) 
3713081631934410656 
>>> hash(X(1.0, 2.0)) 
3713081631934410656 
>>> hash(X(1.0, 3.0)) 
3713081631933328131 

Wenn Sie möchten, können Sie Ihre eigenen __init__ mit dem docstring dass (vermutlich) bietet Ihrem IDE mit Typenannotation Hinweise definieren.

+0

Ich spielte mit dieser Lösung für einige Zeit. Es versteht die Semantik des Wertobjekts genau, hat aber die gleichen Probleme mit der Typinferenz wie namedtuple. Das Hinzufügen von '__init__' hilft nicht wirklich: ohne "magischen" String wie 'self.a = a' hat PyCharm keine Ahnung, dass er die Typdeklaration eines Arguments mit einem Objektattribut verknüpfen soll. Ich habe deinen Code benutzt, um etwas zu bauen, das für mich funktioniert, aber es ist alles andere als perfekt. Ich füge es jetzt als Update zur ursprünglichen Frage an. –

Verwandte Themen