Decorator mit Argumenten & Zugriff auf Methoden Instanz

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
Antworten
Boa
User
Beiträge: 190
Registriert: Sonntag 25. Januar 2009, 12:34

Hi,

Ich möchte in einem Decorator, der mehrere Argumente mitbekommt Zugriff auf die Instanz der geschmückten Methode. Ich habe folgenden Decorator und möchte darin instance_of_decorated_method.handle_error() aufrufen, weil ich hoffe, dass das meinen Code lesbarer macht. Wenn ich so darüber nachdenke ist der Code bereits ziemlich unleserlich, wenn er dadurch besser lesbar werden soll. Aber einen Versuch ist es wert :)

Code: Alles auswählen

def retry(ExceptionToCheck, tries=4, delay=3, backoff=2, logger=None):
    """Retry calling the decorated function using an exponential backoff.

    http://www.saltycrane.com/blog/2009/11/trying-out-retry-decorator-python/
    original from: http://wiki.python.org/moin/PythonDecoratorLibrary#Retry

    :param ExceptionToCheck: the exception to check. may be a tuple of
        excpetions to check
    :type ExceptionToCheck: Exception or tuple
    :param tries: number of times to try (not retry) before giving up
    :type tries: int
    :param delay: initial delay between retries in seconds
    :type delay: int
    :param backoff: backoff multiplier e.g. value of 2 will double the delay
        each retry
    :type backoff: int
    :param logger: logger to use. If None, print
    :type logger: logging.Logger instance
    """
    def deco_retry(f):
        def f_retry(*args, **kwargs):
            mtries, mdelay = tries, delay
            try_one_last_time = True
            while mtries > 1:
                try:
                    return f(*args, **kwargs)
                    try_one_last_time = False
                    break
                except ExceptionToCheck, e:
                    msg = "%s, Retrying in %d seconds..." % (str(e), mdelay)
                    if logger:
                        logger.error(msg)
                    time.sleep(mdelay)
                    mtries -= 1
                    mdelay *= backoff
            if try_one_last_time:
                return f(*args, **kwargs)
            return
        return f_retry  # true decorator
    return deco_retry
Danke,
Boa
BlackJack

@Boa: Das erste Argument einer Methode, also auch einer dekorierten ist doch das Exemplar auf dem sie aufgerufen wurde. Du müsstest das halt nur explizit benennen, wie das bei Methoden üblich ist.

Code: Alles auswählen

        def f_retry(self, *args, **kwargs):
            mtries, mdelay = tries, delay
            try_one_last_time = True
            while mtries > 1:
                try:
                    return f(self, *args, **kwargs)
Der Code scheint mir übrigens auch falsch zu sein. Kannst Du mal erklären unter welchen Umständen die beiden letzten Zeilen innerhalb des ``try``-Blocks jemals ausgeführt werden sollen? Falls Du das nicht kannst, ist `try_one_last_time` immer `True` und dann kann man sich das auch sparen. Ebenfalls sparen kann man sich `mtries` und `mdelay`. Und statt der ``while``-Schleife wäre eine ``for``-Schleife einfacher.
Boa
User
Beiträge: 190
Registriert: Sonntag 25. Januar 2009, 12:34

*Haha*, ja du hast recht, der Code ist merkwürdig und ich sollte ihn mir zumindest richtig durchlesen bevor ich ihn kopiere ^^
Da habe ich saltycrane als zu vertrauenswürdig eingestuft, allerdings wurde der Code dort schon entsprechend verbessert, sonst hätte ich noch einen Post dazu geschrieben: http://www.saltycrane.com/blog/2009/11/ ... or-python/
Ich werde mir den Code trotzdem noch genauer zu Gemüte führen; ich bin ja lernfähig ;)
Toll, dass das so einfach geht. Ist schon wieder eine Weile her, dass ich mich mit Python Decorators herumgeschlagen habe.

Danke, deine Hilfe ist Gold wert :)

[EDIT] Ich habe f_retry wie folgt geändert. Die Klasse kann nun eine Methode _handle_error definieren um auf Fehler zu reagieren, wie z.B. eine Klassen spezifische debug Meldung zu loggen.

[EDIT2] Die Parameter müssen anscheinend lokal kopiert werden, bevor man sie verändern kann. Auch wenn ich den Grund dafür nicht erkenne.

Code: Alles auswählen

       import time
#from functools import wraps


def retry(ExceptionToCheck, tries=4, delay=3, backoff=2):
    """Retry calling the decorated function using an exponential backoff.

    http://www.saltycrane.com/blog/2009/11/trying-out-retry-decorator-python/
    original from: http://wiki.python.org/moin/PythonDecoratorLibrary#Retry

    :param ExceptionToCheck: the exception to check. may be a tuple of
        exceptions to check
    :type ExceptionToCheck: Exception or tuple
    :param tries: number of times to try (not retry) before giving up
    :type tries: int
    :param delay: initial delay between retries in seconds
    :type delay: int
    :param backoff: backoff multiplier e.g. value of 2 will double the delay
        each retry
    :type backoff: int 
    """
    def deco_retry(f):

        #@wraps(f) #preserve original function name, docstring, arguments list - unnecessary
        def f_retry(self, *args, **kwargs):
            mdelay, mbackoff = (delay, backoff)
            for i in range(1, tries):
                try:
                    return f(*args, **kwargs)
                except ExceptionToCheck, e:
                    if hasattr(self, '_handle_error'):
                        self._handle_error(e)
                    time.sleep(mdelay)
                    mdelay*= mbackoff 
            return f(*args, **kwargs)

        return f_retry  # true decorator

    return deco_retry
Antworten