2016-04-15 12 views
3

Ich habe zwei Klassen A und B, wobei B von A erbt und eine Eigenschaft überschreibt. A ist nicht unter meiner Kontrolle, also kann ich es nicht ändern.Unterscheidung zwischen Anrufern innerhalb und außerhalb der Klassenhierarchie

Der Code sieht wie folgt aus:

class A(): 
    def __init__(self, value): 
     self.value = value 
    @property 
    def value(self): 
     return self._value 
    @value.setter 
    def value(self, value): 
     self._value = value 

class B(A): 
    def __init__(self, value): 
     super(B, self).__init__(value) 
    @property 
    def value(self): 
     return super(B, self).value 
    @value.setter 
    def value(self, value): 
     raise AttributeError("can't set attribute") 

Wenn ich versuche, B(1) ich anrufen offensichtlich AttributeError: can't set attribute zu bekommen.

Ich mag ein anderes Verhalten haben, wenn value von innen Klassenmethoden gesetzt

@value.setter 
def value(self, value): 
    if set from inside class hierarchy: 
     pass 
    else: 
     raise AttributeError("can't set attribute") 

Das Modul inspect scheint nicht mir genügend Informationen zu geben, dies zu tun, außer Kontrolle gegen eine Liste von bekannten Funktionen .

+4

Kannst du nicht einfach eingestellt 'self._value' statt' self.value'? Eigenschaften existieren als API für die Außenwelt, nicht unbedingt für das Objekt selbst. – acdr

+0

Sie könnten den Stack-Trace untersuchen, um herauszufinden, wer der Anrufer ist. – Norman

+0

@acdr, leider 'A' ist nicht unter meiner Kontrolle, so kann ich nicht ändern, wie es sich verhält. Ich habe die Frage bearbeitet, um das zu reflektieren. – Dumitru

Antwort

0

Sie den Stapel überprüfen kann, um zu bestimmen, wer angerufen, und ob, dass es in der Klassenhierarchie zu entscheiden, ob oder nicht, es zu erlauben:

import inspect 


def who_called(): 
    frame = inspect.stack()[2][0] 
    if 'self' not in frame.f_locals: 
     return None, None 
    cls = frame.f_locals['self'].__class__ 
    method = frame.f_code.co_name 
    return cls, method 

class A(object): 
    def __init__(self, value): 
     self._value = value 

    @property 
    def value(self): 
     return self._value 

    @value.setter 
    def value(self, value): 
     self._value = value 

    # Assuming this existed it would also work 
    def change_value(self, value): 
     self.value = value 

Klasse B jetzt überprüft:

class B(A): 
    def __init__(self, value): 
     super(B, self).__init__(value) 

    @property 
    def value(self): 
     return super(B, self).value 

    @value.setter 
    def value(self, value): 
     cls, method = who_called() 
     if cls in B.__mro__ and method in A.__dict__: 
      self._value = value 
     else: 
      raise AttributeError("can't set attribute") 

Beweis:

b = B('does not raise error') 
b.change_value('does not raise error') 
b.value = 'raises error' 
0

Sie könnten den Code verwenden, der den Anruf ausgeführt hat, um festzustellen, ob der Anruf aus der Klasse stammt. Nur eine Ausnahme auslösen, wenn der Anruf nicht mit self.value = gestartet wurde.

import re 
import traceback 

class A(object): 
    def __init__(self, value): 
     self.value = value 
    @property 
    def value(self): 
     return self._value 
    @value.setter 
    def value(self, value): 
     self._value = value 

class B(A): 
    def __init__(self, value): 
     super(B, self).__init__(value) 
    @property 
    def value(self): 
     return super(B, self).value 
    @value.setter 
    def value(self, value): 
     call = traceback.extract_stack(limit=2)[0][3] 
     if re.match(r'self.value\s*=', call): 
      pass 
     else: 
      raise AttributeError("can't set attribute") 

b = B(1) # OK 
b.value = 3 # Exception 

Natürlich bricht diese, sobald Sie Ihre Variablen lostelefonieren self:

self = B(1) # OK 
self.value = 3 # Meh, doesn't fail