Ceci est la douzième collection de conseils et de programmation Python de mon flux @pythonetc.
←
Collections précédentesVous ne pouvez pas modifier les variables de fermeture avec une simple affectation. Python considère l'affectation comme une définition à l'intérieur d'un corps de fonction et ne fait pas du tout de fermeture.
Fonctionne bien, affiche
2
:
def make_closure(x): def closure(): print(x) return closure make_closure(2)()
Et ce code renvoie une
UnboundLocalError: local variable 'x' referenced before assignment
:
def make_closure(x): def closure(): print(x) x *= 2 print(x) return closure make_closure(2)()
Pour faire fonctionner le code, utilisez
nonlocal
. Cela indique explicitement à l'interpréteur de ne pas considérer l'affectation comme une définition:
def make_closure(x): def closure(): nonlocal x print(x) x *= 2 print(x) return closure make_closure(2)()
Parfois, lors d'une itération, vous devez savoir quel élément est traité, en premier ou en dernier. Cela peut être facilement déterminé à l'aide d'un indicateur explicite:
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, ]
Bien sûr, vous pouvez gérer le premier élément en dehors de la boucle. Cela semble plus propre, mais conduit à une duplication partielle du code. De plus, il ne sera pas si facile de le faire lorsque vous travaillez avec un résumé
iterable
:
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
Vous pouvez également utiliser
enumerate
et effectuer une vérification
i == 0
(cela ne fonctionne que pour déterminer le premier élément, pas le dernier), cependant, la meilleure solution serait un générateur qui retourne les
first
et
last
drapeaux avec l'élément
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
Maintenant, la fonction d'origine pourrait ressembler à ceci:
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
Si vous devez mesurer le temps écoulé entre deux événements, utilisez
time.monotonic()
au lieu de
time.time()
.
time.monotonic()
ne change jamais dans la petite direction, même lors de la mise à jour de l'horloge système:
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()
Les gestionnaires de contexte imbriqués ne savent généralement pas qu'ils sont imbriqués. Vous pouvez leur en parler en créant des gestionnaires internes à l'aide d'un gestionnaire externe:
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()

Lorsque vous devez transmettre des informations sur une chaîne d'appels, la première chose qui vous vient à l'esprit est de transmettre des données sous forme d'arguments de fonction.
Dans certains cas, il peut être beaucoup plus pratique de modifier toutes les fonctions de la chaîne pour transférer une nouvelle donnée. Au lieu de cela, vous pouvez spécifier un contexte qui sera utilisé par toutes les fonctions de la chaîne. Comment faire
La solution la plus simple consiste à utiliser une variable globale. En Python, vous pouvez également utiliser des modules et des classes comme gardes du contexte car, à proprement parler, ce sont aussi des variables globales. Vous le faites probablement déjà régulièrement, par exemple pour la journalisation.
Si votre application est multithread, les variables globales ordinaires ne fonctionneront pas pour vous, car elles ne sont pas thread-safe. À chaque instant, vous pouvez avoir plusieurs chaînes d'appels et chacune d'elles a besoin de son propre contexte. Le module de
threading
vous aidera, il fournit un objet
threading.local()
, qui est thread-safe. Vous pouvez y stocker des données avec un accès simple aux attributs:
threading.local().symbol = '@'
.
Cependant, les deux approches décrites ne sont pas sécurisées pour la concurrence, c'est-à-dire qu'elles ne conviennent pas à la chaîne d'appels Coroutine, dans laquelle le système appelle non seulement des fonctions, mais attend également qu'elles soient exécutées. Lorsqu'une coroutine s'exécute en
await
, un flux d'événements peut déclencher une autre coroutine à partir d'une chaîne différente. Cela ne fonctionnera pas:
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'), ))
Vous pouvez résoudre ce problème en forçant le cycle à définir et à restaurer le contexte chaque fois que vous basculez entre les coroutines. Vous pouvez implémenter ce comportement en utilisant le module
contextvars
, qui est disponible depuis Python 3.7.
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'), ))