2012-10-19 5 views
5

Wenn ich im Wesentlichen an einer Implementierung des benutzerdefinierten Enumerationstyps arbeitete, stieß ich auf eine Situation, in der ich separate fast identische Unterklassen von beiden int und long ableiten musste, da sie unterschiedliche Klassen in Python sind. Dies scheint etwas ironisch zu sein, da Instanzen der beiden in der Regel austauschbar verwendet werden können, da sie zum größten Teil nur automatisch erstellt werden, wenn dies erforderlich ist.Vermeiden Sie zwei verschiedene numerische Unterklassen (int und long)?

Was ich habe funktioniert gut, aber im Geiste von DRY (Do not Repeat Yourself), kann ich nicht helfen, aber frage mich, ob es keine bessere oder zumindest eine prägnantere Möglichkeit, dies zu erreichen . Das Ziel ist es, Unterklassen-Instanzen zu haben, die überall - oder so nah wie möglich - verwendet werden können, die Instanzen ihrer Basisklassen hätten sein können. Idealerweise sollte dies automatisch geschehen, ähnlich wie die eingebaute int() tatsächlich eine long zurückgibt, wann immer sie eine benötigt.

Hier ist meine aktuelle Implementierung:

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) 

Antwort

2

So können Sie das DRY-Problem durch Mehrfachvererbung lösen. Leider spielt es nicht gut mit __slots__ (es verursacht Kompilierungszeit TypeError s), also musste ich das weg lassen. Hoffentlich verschwenden die Werte nicht zu viel Speicher für Ihren Anwendungsfall.

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

Gute Antwort, aber behandelt 'NamedInt ('HexBased', 'Deadbeef', 16)' nicht. – martineau

+0

Hmm, guter Punkt. Ich denke, es kann mit Varargs behoben werden. Ich werde das bearbeiten. – Blckknght

+0

Danke für die Lösung. War schwer zu entscheiden zwischen dieser und @ eryksuns Antwort, da beide das DRY-Thema sehr gut ansprechen - aber letztendlich dieses gewählt haben, weil es das einfachste und verständlichste IMHO ist. Übrigens ist es möglich, ein '__slots__'-Attribut nur der' NamedInt'-Unterklasse hinzuzufügen (wie es Eryks getan hat), die diese Notwendigkeit für den wahrscheinlich häufigeren 'int'-Fall zu adressieren scheint (und _ist_ ein wichtiges Merkmal für die beabsichtigte Verwendung). – martineau

2

the allocator Aufschalten lassen Sie ein Objekt des entsprechenden Typs zurück.

+0

Zugegeben dies die Notwendigkeit für die 'NamedWholeNumber' Klasse beseitigen würde (und wahrscheinlich Mühe wert sein), aber es scheint, wie die meisten der Code, der gewesen war Es würde nur am Ende in die 'NamedInt .__ new __()' Methode verschoben werden und ich brauche immer noch eine separate 'NamedLong'subclass - oder fehle ich etwas? – martineau

+0

Das hört sich richtig an, aber bedenken Sie, dass Python 2.x separate 'int' und' long' Klassen hat, unabhängig von dem an 'int() 'übergebenen Wert. –

2

Hier ist eine Klasse Dekorateur Version:

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

Auf jeden Fall führend das Paket IMHO. Ich versuche mich zwischen ihm und @ Blckknght zu entscheiden. Ich mag diese Tatsache, dass Ihre die '__slots__' Optimierung unterstützt, nicht-dezimale String-Werte, und erfordert nur einzelne Vererbung. – martineau

+0

Leider habe ich beschlossen, @ Blckknghts Antwort aus den Gründen zu nehmen, die ich in einem Kommentar angegeben habe.Es war eine mühsame Entscheidung zu treffen, und dennoch scheint Ihr Ansatz völlig durchführbar und nur ein guter durch die meisten Maßnahmen. Ich fand auch Ihren Ansatz, Klassenzeichner sehr clever zu implementieren - und lernte daraus einige neue Techniken. Vielen Dank! – martineau

Verwandte Themen