Es ist eine neue Auswahl von Tipps und Tricks zu Python und Programmierung von meinem Telegramm-Kanal @pythonetc.
←
Frühere VeröffentlichungenSie können Abschlussvariablen nicht mutieren, indem Sie sie einfach zuweisen. Python behandelt die Zuweisung als Definition innerhalb eines Funktionskörpers und schließt überhaupt nicht.
Funktioniert gut, druckt
2
:
def make_closure(x): def closure(): print(x) return closure make_closure(2)
UnboundLocalError: local variable 'x' referenced before assignment
:
def make_closure(x): def closure(): print(x) x *= 2 print(x) return closure make_closure(2)()
Damit es funktioniert, sollten Sie nicht
nonlocal
. Der Interpreter wird ausdrücklich angewiesen, die Zuweisung nicht als Definition zu behandeln:
def make_closure(x): def closure(): nonlocal x print(x) x *= 2 print(x) return closure make_closure(2)()
Manchmal möchten Sie während der Iteration wissen, ob dies der erste oder der letzte Elementschritt der Iteration ist. Ein einfacher Weg, dies zu handhaben, ist die Verwendung eines expliziten Flags:
def sparse_list(iterable, num_of_zeros=1): result = [] zeros = [0 for _ in range(num_of_zeros)] first = True for x in iterable: if not first: result += zeros result.append(x) first = False return result assert sparse_list([1, 2, 3], 2) == [ 1, 0, 0, 2, 0, 0, 3, ]
Sie können auch das erste Element außerhalb der Schleife verarbeiten, was klarer erscheint, aber bis zu einem gewissen Grad zu einer Codeduplizierung führt. Es ist auch nicht einfach, mit abstrakten iterablen Elementen zu arbeiten:
def sparse_list(iterable, num_of_zeros=1): result = [] zeros = [0 for _ in range(num_of_zeros)] iterator = iter(iterable) try: result.append(next(iterator)) except StopIteration: return [] for x in iterator: result += zeros result.append(x) return result
Sie können auch
enumerate
und nach
i == 0
(funktioniert nur zur Erkennung des ersten Elements, nicht des letzten Elements), aber die ultimative Lösung könnte ein Generator sein, der das
first
und das
last
Flag zusammen mit dem Element eines zurückgibt iterable:
def first_last_iter(iterable): iterator = iter(iterable) first = True last = False while not last: if first: try: current = next(iterator) except StopIteration: return else: current = next_one try: next_one = next(iterator) except StopIteration: last = True yield (first, last, current) first = False
Die anfängliche Funktion kann nun folgendermaßen aussehen:
def sparse_list(iterable, num_of_zeros=1): result = [] zeros = [0 for _ in range(num_of_zeros)] for first, last, x in first_last_iter(iterable): if not first: result += zeros result.append(x) return result
Wenn Sie die Zeit zwischen zwei Ereignissen messen möchten, sollten Sie
time.monotonic()
anstelle von
time.time()
.
time.monotonic()
geht niemals rückwärts, selbst wenn die Systemuhr aktualisiert wird:
from contextlib import contextmanager import time @contextmanager def timeit(): start = time.monotonic() yield print(time.monotonic() - start) def main(): with timeit(): time.sleep(2) main()
Verschachtelte Kontextmanager wissen normalerweise nicht, dass sie verschachtelt sind. Sie können sie wissen lassen, indem Sie innere Kontextmanager durch den äußeren hervorbringen:
from contextlib import AbstractContextManager import time class TimeItContextManager(AbstractContextManager): def __init__(self, name, parent=None): super().__init__() self._name = name self._parent = parent self._start = None self._substracted = 0 def __enter__(self): self._start = time.monotonic() return self def __exit__(self, exc_type, exc_value, traceback): delta = time.monotonic() - self._start if self._parent is not None: self._parent.substract(delta) print(self._name, 'total', delta) print(self._name, 'outer', delta - self._substracted) return False def child(self, name): return type(self)(name, parent=self) def substract(self, n): self._substracted += n timeit = TimeItContextManager def main(): with timeit('large') as large_t: with large_t.child('medium') as medium_t: with medium_t.child('small-1'): time.sleep(1) with medium_t.child('small-2'): time.sleep(1) time.sleep(1) time.sleep(1) main()

Wenn Sie einige Informationen über die Aufrufkette weitergeben möchten, verwenden Sie normalerweise den einfachsten Weg: Sie übergeben sie als Funktionsargumente.
In einigen Fällen kann es jedoch äußerst unpraktisch sein, alle Funktionen in der Kette zu ändern, um neue Daten zu verbreiten. Stattdessen möchten Sie möglicherweise einen Kontext einrichten, der von allen Funktionen in der Kette verwendet wird. Wie kann dieser Kontext technisch gemacht werden?
Die einfachste Lösung ist eine globale Variable. In Python können Sie auch Module und Klassen als Kontexthalter verwenden, da es sich streng genommen auch um globale Variablen handelt. Sie tun es wahrscheinlich täglich für Dinge wie Logger.
Wenn Ihre Anwendung über mehrere Threads verfügt, funktioniert eine reine globale Variable für Sie nicht, da sie nicht threadsicher ist. Möglicherweise wird mehr als eine Anrufkette gleichzeitig ausgeführt, und jede von ihnen benötigt einen eigenen Kontext. Das
threading
Modul behandelt Sie und stellt das
threading.local()
-Objekt bereit, das threadsicher ist. Speichern Sie dort alle Daten, indem Sie einfach auf die Attribute zugreifen:
threading.local().symbol = '@'
.
Beide Ansätze sind jedoch nicht parallel, was bedeutet, dass sie nicht für die Coroutine-Aufrufkette funktionieren, bei der Funktionen nicht nur aufgerufen werden, sondern auch abgewartet werden können. Sobald eine Coroutine
await
, kann eine Ereignisschleife eine völlig andere Coroutine aus einer völlig anderen Kette ausführen. Das wird nicht funktionieren:
import asyncio import sys global_symbol = '.' async def indication(timeout): while True: print(global_symbol, end='') sys.stdout.flush() await asyncio.sleep(timeout) async def sleep(t, indication_t, symbol='.'): loop = asyncio.get_event_loop() global global_symbol global_symbol = symbol task = loop.create_task( indication(indication_t) ) await asyncio.sleep(t) task.cancel() loop = asyncio.get_event_loop() loop.run_until_complete(asyncio.gather( sleep(1, 0.1, '0'), sleep(1, 0.1, 'a'), sleep(1, 0.1, 'b'), sleep(1, 0.1, 'c'), ))
Sie können dies beheben, indem Sie die Schleife festlegen und den Kontext jedes Mal wiederherstellen, wenn zwischen Coroutinen gewechselt wird. Sie können dies mit dem
contextvars
Modul seit Python 3.7 tun.
import asyncio import sys import contextvars global_symbol = contextvars.ContextVar('symbol') async def indication(timeout): while True: print(global_symbol.get(), end='') sys.stdout.flush() await asyncio.sleep(timeout) async def sleep(t, indication_t, symbol='.'): loop = asyncio.get_event_loop() global_symbol.set(symbol) task = loop.create_task(indication(indication_t)) await asyncio.sleep(t) task.cancel() loop = asyncio.get_event_loop() loop.run_until_complete(asyncio.gather( sleep(1, 0.1, '0'), sleep(1, 0.1, 'a'), sleep(1, 0.1, 'b'), sleep(1, 0.1, 'c'), ))