Erklärung "get around issues with function local scope and reassigning variables"

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
Serpens66
User
Beiträge: 259
Registriert: Montag 15. Dezember 2014, 00:31

Hallo :)

Ich bin auf folgendes python skript gestoßen:
https://github.com/tomasbasham/ratelimi ... _init__.py

Trotz der Erklärung:
# To get around issues with function local scope
# and reassigning variables, we wrap the time
# within a list. When updating the value we're
# not reassigning `last_called`, which would not
# work, but instead reassigning the value at a
# particular index.
last_called = [0.0]
Verstehe ich nicht, warum last_called nicht einfach = 0.0 gesetzt werden kann.
Kann mir das jemand auf deutsch erklären, oder Stichworte auf Deutsch nennen, die ich in einem deutschen Tutorial nachlesen kann?

Auf stackoverflow liest man als Erklärung noch:
# It's a list because simple types like float are constant when captured by a closure. By making it a list, the list is constant, but its contents are not.
Leider hilft das auch noch nicht um es zu verstehen.

Wenn ich nach "function scope" suche, dann stoße ich auf Erklärungen zu lokalen und globalen Variablen. Wüsste aber nicht, warum das in diesem Fall zu einem Problem werden sollte? Liegt das an der Verwendung von decorator/wrapper? Oder den Threads?
BlackJack

In der `wrapper()`-Funktion wird der Wert in der Liste ja geändert. Das geht (in Python 2) nur *so*, denn wenn dort einfach eine Zuweisung an `last_called` stehen würde, dann wäre damit `last_called` automatisch ein lokaler Name der Funktion `wrapper()` und nicht der Name `last_called` in der umgebenden Funktion.
__deets__
User
Beiträge: 14539
Registriert: Mittwoch 14. Oktober 2015, 14:29

Ganz einfach:

Code: Alles auswählen

def test():
    x = 100
    def inc_x():
         x += 1
    return inc_x 

test()()

Aufgabe: wie bekommst du das hin, ohne x global zu machen?
BlackJack

Ich denke ich hätte da einfach eine Klasse geschrieben die man auch ohne einen Kommentar versteht. Das wäre wahrscheinlich auch nicht länger als wenn man eine Erklärung dazu schreiben muss. :-)
Benutzeravatar
kbr
User
Beiträge: 1487
Registriert: Mittwoch 15. Oktober 2008, 09:27

__deets__ hat geschrieben:Aufgabe: wie bekommst du das hin, ohne x global zu machen?
Eine klassenbasierte Implementierung des Decorators wäre hier die bessere Lösung gewesen. Die eignet sich für genau solche Fälle.
__deets__
User
Beiträge: 14539
Registriert: Mittwoch 14. Oktober 2015, 14:29

Oder in Python 3 nonlocal verwenden.
Benutzeravatar
kbr
User
Beiträge: 1487
Registriert: Mittwoch 15. Oktober 2008, 09:27

Stimmt. Aber wer mag schon nonlocal ... :wink:
__deets__
User
Beiträge: 14539
Registriert: Mittwoch 14. Oktober 2015, 14:29

Ich. Objects are a poor mans closure. And closures are a poor mans objects. Man muss es nicht übertreiben, aber auch mal ohne Klasse auskommen ist klasse. Hust.
Serpens66
User
Beiträge: 259
Registriert: Montag 15. Dezember 2014, 00:31

vielen Dank :)

und warum werden Listen anders behandelt als Konstanten?
Und da du BlackJack Python 2 erwähnst, in Python 3 gibt es die Problematik nicht?

Und du schreibst man könnte das ganze auch in eine Klasse verpacken. Das fänd ich auch schöner.
Also einfach das ganze skript in eine Klasse verpacken und self.last_call draus machen?

Wie würde dann die Anwendung aussehen?
Aktuell ist es ja einfach:

Code: Alles auswählen

@rate_limited(1, 2)
    def test(a):
        return a
Wenn meine Klasse nun

Code: Alles auswählen

class Rate:
heißt, wäre es dann zb.

Code: Alles auswählen

rates = Rate()
@rates.rate_limited(1,2)    
    def test(a):
        return a 
?
( ich teste es einfach mal und editiere dann hier, falls ich schnell genug bin)
...
ja scheint so zu funktionieren :)
__deets__
User
Beiträge: 14539
Registriert: Mittwoch 14. Oktober 2015, 14:29

Listen werden nicht anders behandelt. Wenn du

liste = []

machen würdest, hättest du das gleiche Problem. Aber Listen sind veränderbar! Und weil

liste[0] = 100

keine Zuweisung an einen Namen, sondern die Veränderung einer gegebenen Datenstruktur bedeutet, stellt es einen etwas hässlichen workaround dar.
__deets__
User
Beiträge: 14539
Registriert: Mittwoch 14. Oktober 2015, 14:29

Und in meinem Beitrag #2,steht auch was in Python3 anders ist.
Serpens66
User
Beiträge: 259
Registriert: Montag 15. Dezember 2014, 00:31

Danke :)
BlackJack

@Serpens66: Die Anwendung würde als Klasse genau so aussehen, nur der Name wäre halt ein Klassenname. Es sei denn man weicht hier von der Konvention für Klassennamen ab, dann sieht's genau so aus.
Sirius3
User
Beiträge: 17749
Registriert: Sonntag 21. Oktober 2012, 17:20

Ich würde das ja so schreiben:

Code: Alles auswählen

class WaitingLock(threading.RLock):
    def __init__(self, period, every):
        self.frequency = abs(every) / float(clamp(period))
        self.next_call = 0
        
    def wait(self):
        time.sleep(max(time.time() - self.next_call, 0))
        self.next_call = time.time() + self.frequency

def rate_limited(period=1, every=1.0, func=None):
    '''
    Prevent a method from being called
    if it was previously called before
    a time widows has elapsed.
    :param int period: Maximum method invocations within a period. Must be greater than 0.
    :param float every: A dampening factor (in seconds). Can be any number greater than 0.
    :return: Decorated function that will forward method invocations if the time window has elapsed.
    '''
    if func is None:
        return partial(rate_limited, period, every)
    lock = WaitingLock(period, every)
    def wrapper(*args, **kargs):
        '''Decorator wrapper function'''
        with lock:
            lock.wait()
        return func(*args, **kargs)
    return wrapper
Antworten