Frage

Nachdem nach einer Möglichkeit gesucht Unveränderlichkeit von benutzerdefinierten Typen zu erzwingen und keine befriedigende Antwort gefunden zu haben ich mit meiner eigenen Schuss auf eine Lösung in Form einer Metaklasse kam:

class ImmutableTypeException( Exception ): pass

class Immutable( type ):
   '''
   Enforce some aspects of the immutability contract for new-style classes:
    - attributes must not be created, modified or deleted after object construction
    - immutable types must implement __eq__ and __hash__
   '''

   def __new__( meta, classname, bases, classDict ):
      instance = type.__new__( meta, classname, bases, classDict )

      # Make sure __eq__ and __hash__ have been implemented by the immutable type.
      # In the case of __hash__ also make sure the object default implementation has been overridden. 
      # TODO: the check for eq and hash functions could probably be done more directly and thus more efficiently
      #       (hasattr does not seem to traverse the type hierarchy)
      if not '__eq__' in dir( instance ):
         raise ImmutableTypeException( 'Immutable types must implement __eq__.' )

      if not '__hash__'  in dir( instance ):
         raise ImmutableTypeException( 'Immutable types must implement __hash__.' )

      if _methodFromObjectType( instance.__hash__ ):
         raise ImmutableTypeException( 'Immutable types must override object.__hash__.' )

      instance.__setattr__ = _setattr
      instance.__delattr__ = _delattr

      return instance

   def __call__( self, *args, **kwargs ):

      obj = type.__call__( self, *args, **kwargs )
      obj.__immutable__ = True

      return obj

def _setattr( self, attr, value ):

   if '__immutable__' in self.__dict__ and self.__immutable__:
      raise AttributeError( "'%s' must not be modified because '%s' is immutable" % ( attr, self ) )

   object.__setattr__( self, attr, value )

def _delattr( self, attr ):
   raise AttributeError( "'%s' must not be deleted because '%s' is immutable" % ( attr, self ) )

def _methodFromObjectType( method ):
   '''
   Return True if the given method has been defined by object, False otherwise.
   '''
   try:
      # TODO: Are we exploiting an implementation detail here? Find better solution! 
      return isinstance( method.__objclass__, object )
   except:
      return False

Während jedoch der allgemeine Ansatz scheint recht gut zu funktionieren gibt es noch einige zweifelhaften Implementierungsdetails (auch TODO Kommentare in Code sehen):

  1. Wie kann ich überprüfen, ob eine bestimmte Methode überall in der Typenhierarchie implementiert wurde?
  2. Wie kann ich überprüfen, welche Art ist der Ursprung einer Methodendeklaration (das heißt als Teil der ein Verfahren Typ definiert wurde)?
War es hilfreich?

Lösung

Spezielle Methoden werden immer nachgeschlagen auf dem type , nicht die Instanz . So hasattr muss auch auf die Typ angewendet werden . Z.

>>> class A(object): pass
... 
>>> class B(A): __eq__ = lambda *_: 1
... 
>>> class C(B): pass
... 
>>> c = C()
>>> hasattr(type(c), '__eq__')
True

Überprüfen hasattr(c, '__eq__') wäre irreführend, da es könnte fälschlicherweise „fangen“ eine pro-Instanz Attribut __eq__ in c selbst definiert, die als spezielle Methode nicht handeln würde (beachten Sie, dass im konkreten Fall von __eq__ Sie immer einen True sehen werden resultieren aus hasattr, weil Vorfahrklasse object definiert es, und Vererbung kann immer nur „add“ Attribute, nie „subtrahieren“ any; -).

Um zu überprüfen, die Vorfahrklasse zuerst ein Attribut definiert (und damit genaue Definition, die verwendet wird, wenn die Suche nur von der Art ist):

import inspect

def whichancestor(c, attname):
  for ancestor in inspect.getmro(type(c)):
    if attname in ancestor.__dict__:
      return ancestor
  return None

Es ist am besten inspect für solche Aufgaben zu verwenden, da es mehr funktioniert im Großen und Ganzen als ein direkter Zugriff des __mro__ Attribut auf type(c).

Andere Tipps

Dieses metaclass erzwingt "flach" Unveränderlichkeit. Zum Beispiel ist es nicht verhindern

immutable_obj.attr.attrs_attr = new_value
immutable_obj.attr[2] = new_value

Je nachdem, ob attrs_attr durch das Objekt gehört oder nicht, die in Betracht gezogen werden könnten wahr Unveränderlichkeit zu verletzen. Z.B. es könnte in der folgenden führen, die nicht für eine unveränderliche Art geschehen sollte:

>>> a = ImmutableClass(value)
>>> b = ImmutableClass(value)
>>> c = a
>>> a == b
True
>>> b == c
True
>>> a.attr.attrs_attr = new_value
>>> b == c
False

Möglicherweise könnte man diesen Mangel beheben durch zwingende getattr als auch für jedes Attribut eine Art unveränderlichen Wrapper zurückzukehren er zurückkehrt. Es könnte schwierig sein. Blocking direkte setattr Anrufe getan werden könnte, aber was über die Methoden des Attributs, das seine Attribute in ihrem Code festgelegt? Ich kann von Ideen denken, aber es wird ziemlich Meta erhalten, in Ordnung.

Auch dachte ich, das eine geschickte Nutzung Ihrer Klasse sein würde:

class Tuple(list):
    __metaclass__ = Immutable

Aber es hat keinen Tupel machen, wie ich gehofft hatte.

>>> t = Tuple([1,2,3])
>>> t.append(4)
>>> t
[1, 2, 3, 4]
>>> u = t
>>> t += (5,)
>>> t
[1, 2, 3, 4, 5]
>>> u
[1, 2, 3, 4, 5]

Ich denke, die Liste der Methoden sind meist oder vollständig auf der C-Ebene implementiert, so dass ich Ihre metaclass nehme an, in ihnen keine Gelegenheit zu Intercept Zustandsänderungen hat.

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top