Seite 1 von 1

Frage zu __getattr__

Verfasst: Dienstag 14. Februar 2012, 19:52
von Gremlin
Hallo zusammen,

ich möchte eine meiner Klassen thread sicher gestalten, dh. ich möchte ganz bestimmte Attribute absichern. Ich möchte jedoch nicht für jedes einzelne Attribut (oder Attributgruppen) Wrapper-methoden basteln die sich dann um das jeweilige Lock kümmern. Also das hier:

Code: Alles auswählen

class Foo(object):
    def __init__(self):
        self.lock = RLock()
        self.bar = None

    def get_bar(self):
        self.lock.aquire()
        ...was mit bar machen...
        self.lock.release()
Was ich möchte ist dass das ganze so funktioniert, dass ich wie gewohnt auf die Attribute zugreife. Da hatte ich dann an __getattr__ gedacht, aber das funktioniert ja nur bei Attributen die nicht bereits existieren. Müsste ich das also so implementieren?

Code: Alles auswählen

class Foo(object):
     def __init__(self):
          self.lock = RLock()
          self.__bar = None

     def __getattr__(self, name):
          if name == 'bar':
               self.lock.aquire()
               ...was mit __bar machen....
               self.lock.release()
Oder gibt es da eine andere "magische" Variante?

PS: Wie ich in den Beispielen mit den Attributen umgehe ist nicht 100% das was ich vorhabe. Aber primär gehts mir auch nur um eine mögliche Alternative zur letzten Variante.

Re: Frage zu __getattr__

Verfasst: Dienstag 14. Februar 2012, 20:20
von DasIch
Verwende einfach einen Dekorator der einen Wrapper um die dekorierte Methode zurückgibt und sich um die Locks kümmert.

Re: Frage zu __getattr__

Verfasst: Dienstag 14. Februar 2012, 21:32
von Gremlin
Nunja, ich möcht ja aber möglichst ohne zusätzliche Methoden auskommen.

Re: Frage zu __getattr__

Verfasst: Dienstag 14. Februar 2012, 23:17
von deets
Dann benutz halt ne Metaklasse. Aber ganz *ohne* irgendwas wird's nicht gehen, denn du willst ja was *anders* machen, als Python das normalerweise tut.....

Re: Frage zu __getattr__

Verfasst: Mittwoch 15. Februar 2012, 00:00
von pillmuncher
Einfach alle Methoden-Aufrufe durch einen einzigen, gemeinsamen Mutex zu tunneln, skaliert meistens nicht besonders und führt oft zu einer Ent-Parallelisierung, weil ja alle Aufrufe dadurch hintereinander passieren. Es ist auch meistens besser, einen komplexeren Monitor statt bloßer Locks zu verwenden, und zwar pro Menge von Attributen, deren Zustand einer gemeinsamen Invariante unterliegt. Diese Invariante ist nämlich idR. komplizierter, als bloß "dieses Attribut darf nicht gleichzeitig gelesen und geschrieben werden", wie es durch ein Lock bzw. RLock ermöglicht würde. Einen Ringpuffer etwa, in den von einem Thread geschrieben und aus dem aus einem anderen Thread gelesen wird, kann man damit nicht bauen.

Threadsicherheit bei einem Ringpuffer bedeutet: wenn der Puffer leer ist, muss gewartet werden, bis etwas hineingeschrieben wurde, erst dann kann man wieder lesen. Wenn er voll ist, muss gewartet werden, bis etwas gelesen wurde, erst dann kann man wieder hineinschreiben. Dazu verwendet man eine Liste fester Größe und zwei Zeiger, links und rechts, die sich in Einer-Schritten in dieselbe Rchtung bewegen (wobei rechts vorneweg marschieren darf), die sich aber gegenseitig nie überholen dürfen. Wenn links == rechts ist, ist der Puffer leer, wenn links == (rechts + 1) % Größe des Puffers ist, ist er voll. Damit in den jeweiligen Zuständen leer und voll gewartet wird, benötigt man einen Monitor mit wait sets, der über ein Condition Objekt kontrolliert wird, zB. so:

Code: Alles auswählen

from threading import Thread, Condition

class Ringbuffer(object):

    def __init__(self, size):
        assert size > 0
        self._size = size
        self._items = [None] * size
        self._left = self._right = 0
        self._cond = Condition()

    def is_full(self):
        with self._cond:
            return self._left == (self._right + 1) % self._size

    def is_empty(self):
        with self._cond:
            return self._left == self._right

    def put(self, item):
        with self._cond:
            while self.is_full():
                self._cond.wait()
            self._right += 1
            self._right %= self._size
            self._items[self._right] = item
            print 'put', item
            self._cond.notify()

    def get(self):
        with self._cond:
            while self.is_empty():
                self._cond.wait()
            self._left += 1
            self._left %= self._size
            item = self._items[self._left]
            print 'get', item
            self._cond.notify()
            return item


SENTINEL = object()


def retrieve(buf):
    while True:
        item = buf.get()
        if item is SENTINEL:
            print 'Goodbye!'
            break


some_buffer = Ringbuffer(5)
some_buffer.put(1)
some_buffer.put(2)
some_buffer.put(3)

t = Thread(target=lambda:retrieve(some_buffer))
t.start()

some_buffer.put('joe')
some_buffer.put(5)
some_buffer.put(6)
some_buffer.put(7)
some_buffer.put('jim')
some_buffer.put(SENTINEL)
Ein Monitor ist, wie man sieht, nicht ein Objekt oder eine Funktion, es ist überhaupt kein Ding, sondern ein abstraktes Konzept, das durch Conditions implementiert wird. Es ist ein Design Pattern.

Man kann übrigens, wie DasIch vorgeschlagen hat, auch Dekoratoren verwenden:

Code: Alles auswählen

def locked_with(lock):
    def decorated(method):
        def decorator(self, *args, **kwargs):
            with lock(self):
                return method(self, *args, **kwargs)
        return decorator
    return decorated


def wait_if(is_in_state, get_cond):
    def decorated(method):
        def decorator(self, *args, **kwargs):
            cond = get_cond(self)
            with cond:
                while is_in_state(self):
                    cond.wait()
                result = method(self, *args, **kwargs)
                cond.notify()
                return result
        return decorator
    return decorated


class Ringbuffer(object):

    def __init__(self, size):
        assert size > 0
        self._size = size
        self._items = [None] * size
        self._left = self._right = 0
        self._cond = Condition()

    def cond(self):
        return self._cond

    @locked_with(cond)
    def is_full(self):
        return self._left == (self._right + 1) % self._size

    @locked_with(cond)
    def is_empty(self):
        return self._left == self._right

    @wait_if(is_full, cond)
    def put(self, item):
        self._right += 1
        self._right %= self._size
        self._items[self._right] = item
        print 'put', item

    @wait_if(is_empty, cond)
    def get(self):
        self._left += 1
        self._left %= self._size
        item = self._items[self._left]
        print 'get', item
        return item
get_cond in wait_if() ist leider notwendig, damit man pro Klasse mehrere Conditions haben kann und nicht auf den Namen _cond festgelegt ist.

Wie Monitore funktionieren, und vieles mehr, steht in diesem Buch.

Gruß,
Mick.

Re: Frage zu __getattr__

Verfasst: Mittwoch 15. Februar 2012, 04:04
von pillmuncher
Übrigens könnte man das einfache Synchronisieren über einzelne Attribute auch mittels Deskriptoren lösen:

Code: Alles auswählen

from threading import Thread, RLock
import time


_syncs = {}

class synchronized_property(property):
    def __init__(self, *args, **kwargs):
        property.__init__(self, *args, **kwargs)
        self._lock = {}
    def __get__(self, obj, owner=None):
        if obj is None:
            return self
        with self._lock[obj]:
            print 'locked for get'
            return property.__get__(self, obj, owner)
    def __set__(self, obj, value):
        with self._lock[obj]:
            print 'locked for set'
            property.__set__(self, obj, value)


class Synchronizable(object):

    class __metaclass__(type):
        def __new__(meta, name, bases, dct):
            cls = type.__new__(meta, name, bases, dct)
            _syncs[cls] = [
                attrib for attrib in dct.itervalues()
                    if isinstance(attrib, synchronized_property)
            ]
            return cls

    def __new__(cls, *args, **kwargs):
        o = object.__new__(cls)
        for sync in _syncs[cls]:
            sync._lock[o] = RLock()
        return o


class Foo(Synchronizable):

    def __init__(self):
        self._b = 0

    @synchronized_property
    def bar(self):
        return self._b

    @bar.setter
    def bar(self, b):
        self._b = b


def set_bar(g):
    g.bar = 7

f = Foo(3)
print f.bar
Thread(target=lambda:set_bar(f)).start()
time.sleep(1)
print f.bar
Ist halt bisserl magisch.

Noch was: wenn du ein Attribut als privat markieren willst, tu das mit einem Unterstrich, nicht mit zwei. Zwei Unterstriche verursachen Name Mangling. Vermutlich möchtest du das nicht.

Re: Frage zu __getattr__

Verfasst: Samstag 24. März 2012, 11:54
von Gremlin
:shock: Viel input, danke pillmuncher. Momentan brauche ich es zwar nicht mehr, aber trotzdem danke. :)