2012-10-19 6 views
5

Quando si lavora su essenzialmente un'implementazione tipo enumerato personalizzato, mi sono imbattuto in una situazione in cui sembra che ho dovuto ricavare separato ancora quasi sottoclassi identici sia da int e long dato che sono distinte classi in Python. Questo sembra un po 'ironico dal momento che le istanze di entrambi possono essere usate in modo intercambiabile perché per la maggior parte vengono semplicemente create automaticamente quando richiesto.Evitare di avere due sottoclassi numeriche diverse (int e long)?

Quello che ho funziona bene, ma nello spirito di DRY (Do not Repeat Yourself), non posso fare a meno di chiedermi se non c'è modo migliore o almeno più succinto di realizzare questo . L'obiettivo è di avere istanze di sottoclassi che possono essere utilizzate ovunque - o il più vicino possibile - che potrebbero essere state le istanze delle loro classi base. Idealmente, ciò dovrebbe avvenire automaticamente in modo simile al modo in cui il int() integrato restituisce effettivamente uno long ogni volta che ne viene rilevato uno.

Ecco il mio attuale implementazione:

class NamedInt(int): 
    """Subclass of type int with a name attribute""" 
    __slots__ = "_name" # also prevents additional attributes from being added 

    def __setattr__(self, name, value): 
     if hasattr(self, name): 
      raise AttributeError(
       "'NamedInt' object attribute %r is read-only" % name) 
     else: 
      raise AttributeError(
       "Cannot add attribute %r to 'NamedInt' object" % name) 

    def __new__(cls, name, value): 
     self = super(NamedInt, NamedInt).__new__(cls, value) 
     # avoid call to this subclass's __setattr__ 
     super(NamedInt, self).__setattr__('_name', name) 
     return self 

    def __str__(self): # override string conversion to be name 
     return self._name 

    __repr__ = __str__ 


class NamedLong(long): 
    """Subclass of type long with a name attribute""" 
    # note: subtypes of variable length 'long' type can't have __slots__ 

    def __setattr__(self, name, value): 
     if hasattr(self, name): 
      raise AttributeError(
       "NamedLong object attribute %r is read-only" % name) 
     else: 
      raise AttributeError(
       "Cannot add attribute %r to 'NamedLong' object" % name) 

    def __new__(cls, name, value): 
     self = super(NamedLong, NamedLong).__new__(cls, value) 
     # avoid call to subclass's __setattr__ 
     super(NamedLong, self).__setattr__('_name', name) 
     return self 

    def __str__(self): 
     return self._name # override string conversion to be name 

    __repr__ = __str__ 

class NamedWholeNumber(object): 
    """Factory class which creates either a NamedInt or NamedLong 
    instance depending on magnitude of its numeric value. 
    Basically does the same thing as the built-in int() function 
    does but also assigns a '_name' attribute to the numeric value""" 
    class __metaclass__(type): 
     """NamedWholeNumber metaclass to allocate and initialize the 
      appropriate immutable numeric type.""" 
     def __call__(cls, name, value, base=None): 
      """Construct appropriate Named* subclass.""" 
      # note the int() call may return a long (it will also convert 
      # values given in a string along with optional base argument) 
      number = int(value) if base is None else int(value, base) 

      # determine the type of named numeric subclass to use 
      if -sys.maxint-1 <= number <= sys.maxint: 
       named_number_class = NamedInt 
      else: 
       named_number_class = NamedLong 

      # return instance of proper named number class 
      return named_number_class(name, number) 

risposta

2

Ecco come è possibile risolvere il problema di DRY tramite ereditarietà multipla. Sfortunatamente, non funziona bene con __slots__ (causa tempo di compilazione TypeError s) quindi ho dovuto lasciarlo. Speriamo che i valori di __dict__ non sprechino troppa memoria per il tuo caso d'uso.

class Named(object): 
    """Named object mix-in. Not useable directly.""" 
    def __setattr__(self, name, value): 
     if hasattr(self, name): 
      raise AttributeError(
       "%r object attribute %r is read-only" % 
       (self.__class__.__name__, name)) 
     else: 
      raise AttributeError(
       "Cannot add attribute %r to %r object" % 
       (name, self.__class__.__name__)) 

    def __new__(cls, name, *args): 
     self = super(Named, cls).__new__(cls, *args) 
     super(Named, self).__setattr__('_name', name) 
     return self 

    def __str__(self): # override string conversion to be name 
     return self._name 

    __repr__ = __str__ 

class NamedInt(Named, int): 
    """NamedInt class. Constructor will return a NamedLong if value is big.""" 
    def __new__(cls, name, *args): 
     value = int(*args) # will raise an exception on invalid arguments 
     if isinstance(value, int): 
      return super(NamedInt, cls).__new__(cls, name, value) 
     elif isinstance(value, long): 
      return NamedLong(name, value) 

class NamedLong(Named, long): 
    """Nothing to see here.""" 
    pass 
+0

Buona risposta, ma non gestisce 'NamedInt ('HexBased', 'deadbeef', 16)'. – martineau

+0

Hmm, buon punto. Penso che possa essere risolto con varargs. Io modificherò per farlo. – Blckknght

+0

Grazie per la correzione. È stato difficile decidere tra questa e la risposta di @ eryksun, poiché entrambi affrontano la questione DRY estremamente bene - ma alla fine l'ho scelto perché è l'IMHO più semplice e comprensibile. A proposito, è possibile aggiungere un attributo '__slots__' alla sola sottoclasse' NamedInt' (come fatto da eryksun) che sembra rispondere a quella necessità del probabile caso 'int' più comune (e _è_ una caratteristica importante per l'uso inteso). – martineau

2

Override the allocator ti consente di restituire un oggetto del tipo appropriato.

class NamedInt(int): 
    def __new__(...): 
    if should_be_NamedLong(...): 
     return NamedLong(...) 
    ... 
+0

Concesso Questo eliminerebbe la necessità per la classe 'NamedWholeNumber' (e probabilmente vale la pena fare), ma sembra che la maggior parte del codice che era stato in esso finirebbe per essere spostato nel metodo 'NamedInt .__ new __()' e avrò ancora bisogno di avere una sottoclasse 'NamedLong' separata - o mi manchi qualcosa? – martineau

+0

Suona bene, ma ricorda che Python 2.x ha classi separate 'int' e' long' indipendentemente dal valore passato a 'int()'. –

2

Ecco una versione di classe decoratrice:

def named_number(Named): 

    @staticmethod 
    def __new__(cls, name, value, base=None): 
     value = int(value) if base is None else int(value, base) 
     if isinstance(value, int): 
      NamedNumber = Named # NamedInt/NamedLong 
     else: 
      NamedNumber = cls = NamedLong 
     self = super(NamedNumber, cls).__new__(cls, value) 
     super(NamedNumber, self).__setattr__('_name', name) 
     return self 

    def __setattr__(self, name, value): 
     if hasattr(self, name): 
      raise AttributeError(
       "'%r' object attribute %r is read-only" % (Named, name)) 
     else: 
      raise AttributeError(
       "Cannot add attribute %r to '%r' object" % (name, Named)) 

    def __repr__(self): 
     return self._name 

    __str__ = __repr__ 

    for k, v in locals().items(): 
     if k != 'Named': 
      setattr(Named, k, v) 

    return Named 

@named_number 
class NamedInt(int): 
    __slots__ = '_name' 

@named_number 
class NamedLong(long): pass 
+0

Sicuramente leader del pacchetto IMHO. Sto cercando di decidere tra questo e quello di @ Blckknght. Mi piace il fatto che il tuo supporta l'ottimizzazione '__slots__', valori di stringa non decimali e richiede solo un'ereditarietà. – martineau

+0

Purtroppo ho deciso di andare con la risposta di @ Blckknght per i motivi indicati in un commento che ho aggiunto ad esso.È stata una scelta ardua da fare, e tuttavia il tuo approccio sembra completamente fattibile e solo un buon punto dalla maggior parte delle misure. Ho anche trovato molto ingegnoso il tuo approccio nell'implementare decoratori di classe - e ho imparato alcune nuove tecniche da esso. Grazie! – martineau