2013-10-22 14 views
11

Sono curioso di trovare un buon modo per definire l'oggetto valore in Python. Per Wikipedia: "value object è un oggetto di piccole dimensioni che rappresenta una semplice entità la cui uguaglianza non è basata sull'identità: cioè due oggetti valore sono uguali quando hanno lo stesso valore, non necessariamente lo stesso oggetto". In Python significa essenzialmente ridefinito i metodi __eq__ e __hash__, nonché l'immutabilità.Come definire un oggetto valore PyCharm in Python?

Lo standard namedtuple sembra una soluzione quasi perfetta con l'eccezione che non funzionano bene con i moderni IDE Python come PyCharm. Intendo dire che IDE non fornirà davvero alcuna informazione utile sulla classe definita come namedtuple. Mentre è possibile allegare docstring a tale classe utilizzando trucco come questo:

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

semplicemente non c'è alcun posto dove mettere descrizione di argomenti del costruttore e specificare i loro tipi. PyCharm è abbastanza intelligente da indovinare gli argomenti per il "costruttore" Point2D, ma dal punto di vista del tipo è cieco.

Questo codice avere alcune informazioni di tipo premuto, ma non è molto utile:

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 vedrà tipi quando costruire nuovi oggetti, ma non capire che point.x e punto.Ý sono galleggianti, quindi non aiuterei a scoprire il loro uso improprio. Inoltre, non mi piace l'idea di ridefinire i metodi "magici" su base routinaria.

Quindi sono alla ricerca di qualcosa che sarà:

  • altrettanto facile da definire come normale classe Python o namedtuple
  • forniscono semantica di valore (uguaglianza, hash, immutabilità)
  • facile da documento in un modo che giocherà bene con IDE

soluzione ideale potrebbe essere la seguente:

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) 

O che:

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 

ho cercato di trovare qualcosa di simile, ma senza successo. Ho pensato che sarebbe saggio chiedere aiuto prima di implementarlo da solo.

UPDATE: Con l'aiuto dell'utente4815162342 sono riuscito a trovare qualcosa che funzioni. Ecco il codice:

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) 

È molto lontano da una soluzione ideale. dichiarazione di classe è simile al seguente:

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 

E 'totale quattro volte per elencare tutti gli attributi: in __slots__, negli argomenti ctor, in docstring e nel corpo ctor. Finora non ho idea di come renderlo meno imbarazzante.

+1

Si noti che 'namedtuple' ha lo scopo principale di fornire sia l'interfaccia tuple (indicizzazione, decompressione), sia l'accesso agli attributi. È stato inventato per la compatibilità con le versioni precedenti delle funzioni utilizzate per restituire tuple, come 'os.stat' o' time.gmtime'. Probabilmente non è la scelta ottimale per un tipo di valore semplice. – user4815162342

+0

Per quanto riguarda i tipi: * PyCharm è abbastanza intelligente da indovinare argomenti per il "costruttore" di Point2D, ma dal punto di vista del tipo è cieco * Forse dovresti usare un linguaggio tipizzato in modo statico? In Python non dovrebbe essere un grosso problema che un IDE sia cieco sui tipi. – user4815162342

+0

Bene, 'namedtuple' fa quasi il lavoro giusto per me. È sicuramente più di un semplice oggetto di valore, ma posso conviverci. Per quanto riguarda l'utilizzo del linguaggio tipizzato staticamente, vorrei poterlo fare. Ma ho un progetto Python sulle mani e sto cercando un modo per rendere lo sviluppo più confortevole. E PyCharm fa già un ottimo lavoro per inferire il tipo di variabili usando le docstring. –

risposta

3

Le vostre esigenze, benché espresse con cura, non mi sono chiare, in parte perché non uso la GUI PyCharm.Ma qui è un tentativo:

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) 

L'utilizzo è simile a questo:

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

In questo modo si ottiene una classe di valore concreto con due di sola lettura slot e un costruttore generato automaticamente, __eq__, e __hash__. Per esempio:

>>> 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 

Se si desidera, è possibile definire il proprio __init__ con la docstring che (presumibilmente) fornisce il vostro IDE con sentori tipo di annotazione.

+0

Ho giocato con questa soluzione per un po 'di tempo. Prende precisamente la semantica dell'oggetto valore, ma ha gli stessi problemi con l'inferenza di tipo "namedtuple". Aggiungere '__init__' non aiuta molto: senza una stringa" magica "come' self.a = a' PyCharm non ha idea che debba collegare la dichiarazione del tipo di un argomento a un attributo dell'oggetto. Ho usato il tuo codice per costruire qualcosa che funzioni per me, ma è tutt'altro che perfetto. Lo sto allegando come aggiornamento alla domanda originale ora. –

Problemi correlati