Ceci est la troisième collection de conseils et de programmation Python de mon 
flux @pythonetc .
Sélections précédentes:
Méthode d'usine
Si vous créez de nouveaux objets dans 
__init__ , il est plus judicieux de les passer prêts en tant qu'arguments et d'utiliser la méthode d'usine pour créer l'objet. Cela séparera la logique métier de la mise en œuvre technique de la création d'objets.
Dans cet exemple, 
__init__ accepte l' 
host et le 
port comme arguments pour créer une connexion à la base de données:
 class Query: def __init__(self, host, port): self._connection = Connection(host, port) 
Option de refactoring:
 class Query: def __init__(self, connection): self._connection = connection @classmethod def create(cls, host, port): return cls(Connection(host, port)) 
Cette approche présente au moins les avantages suivants:
- Le déploiement est simple. Dans les tests, vous pouvez faire une Query(FakeConnection()).
- Une classe peut avoir autant de méthodes d'usine que vous le souhaitez. Vous pouvez créer une connexion non seulement en utilisant l' hostet leport, mais également en clonant une autre connexion, en lisant le fichier de configuration, en utilisant la connexion par défaut, etc.
- Des méthodes d'usine similaires peuvent être transformées en fonctions asynchrones, ce qui est absolument impossible à faire avec __init__.
super ou suivant
La fonction 
super() vous permet de référencer la classe de base. Cela peut être très utile dans les cas où la classe dérivée souhaite ajouter quelque chose à l'implémentation de la méthode, plutôt que de la remplacer complètement.
 class BaseTestCase(TestCase): def setUp(self): self._db = create_db() class UserTestCase(BaseTestCase): def setUp(self): super().setUp() self._user = create_user() 
Le nom super ne signifie rien de «super». Dans ce contexte, cela signifie «plus haut dans la hiérarchie» (par exemple, comme dans le mot «surintendant»). En même temps, 
super() ne fait pas toujours référence à la classe de base; il peut facilement renvoyer une classe enfant. Il serait donc plus correct d'utiliser le nom 
next() , car la classe suivante est renvoyée selon le MRO.
 class Top: def foo(self): return 'top' class Left(Top): def foo(self): return super().foo() class Right(Top): def foo(self): return 'right' class Bottom(Left, Right): pass  
N'oubliez pas que 
super() peut produire des résultats différents en fonction de l'origine de la méthode.
 >>> Bottom().foo() 'right' >>> Left().foo() 'top' 
Espace de noms personnalisé pour créer une classe
Une classe est créée en deux grandes étapes. Tout d'abord, le corps de la classe est exécuté, comme le corps d'une fonction. Dans la deuxième étape, l'espace de noms résultant (qui est renvoyé par 
locals() ) est utilisé par la métaclasse (la valeur par défaut est 
type ) pour créer un objet de la classe.
 class Meta(type): def __new__(meta, name, bases, ns): print(ns) return super().__new__( meta, name, bases, ns ) class Foo(metaclass=Meta): B = 2 
Ce code affiche 
{'__module__': '__main__', '__qualname__':'Foo', 'B': 3} .
Évidemment, si vous introduisez quelque chose comme 
B = 2; B = 3 B = 2; B = 3 , alors la métaclasse ne verra que 
B = 3 , car seule cette valeur est en 
ns . Cette limitation provient du fait que la métaclasse ne commence à fonctionner qu'après l'exécution du corps.
Cependant, vous pouvez intervenir dans la procédure d'exécution en glissant votre propre espace de noms. Un dictionnaire simple est utilisé par défaut, mais vous pouvez fournir votre propre objet, semblable à un dictionnaire, si vous utilisez la méthode 
__prepare__ partir de la métaclasse.
 class CustomNamespace(dict): def __setitem__(self, key, value): print(f'{key} -> {value}') return super().__setitem__(key, value) class Meta(type): def __new__(meta, name, bases, ns): return super().__new__( meta, name, bases, ns ) @classmethod def __prepare__(metacls, cls, bases): return CustomNamespace() class Foo(metaclass=Meta): B = 2 B = 3 
Résultat d'exécution du code:
 __module__ -> __main__ __qualname__ -> Foo B -> 2 B -> 3 
Ainsi, 
enum.Enum protégé contre la 
duplication .
matplotlib
matplotlib est une bibliothèque Python complexe et flexible pour la cartographie. De nombreux produits le prennent en charge, notamment Jupyter et Pycharm. Voici un exemple de rendu d'une fractale simple à l'aide de 
matplotlib : 
https://repl.it/@VadimPushtaev/myplotlib (voir l'image titre de cette publication).
Prise en charge des fuseaux horaires
Python fournit une bibliothèque 
datetime puissante pour travailler avec les dates et les heures. Il est curieux que les objets 
datetime aient une interface spéciale pour supporter les fuseaux horaires (à savoir, l'attribut 
tzinfo ), mais ce module a un support limité pour l'interface mentionnée, donc une partie du travail est assignée à d'autres modules.
Le plus populaire d'entre eux est le 
pytz . Mais le fait est que 
pytz n'est pas entièrement conforme à l'interface 
tzinfo . Ceci est indiqué au tout début de la documentation de 
pytz : "Cette bibliothèque diffère de l'API Python documentée pour les implémentations de tzinfo."
Vous ne pouvez pas utiliser les objets de 
pytz tzinfo comme 
tzinfo . Si vous essayez de le faire, vous risquez d'obtenir un résultat complètement fou:
 In : paris = pytz.timezone('Europe/Paris') In : str(datetime(2017, 1, 1, tzinfo=paris)) Out: '2017-01-01 00:00:00+00:09' 
Notez le décalage +00: 09. Pytz doit être utilisé comme ceci:
 In : str(paris.localize(datetime(2017, 1, 1))) Out: '2017-01-01 00:00:00+01:00' 
De plus, après toute opération arithmétique, vous devez appliquer la 
normalize à vos objets 
datetime pour éviter de modifier les décalages (par exemple, à la frontière de la période d'heure d'été).
 In : new_time = time + timedelta(days=2) In : str(new_time) Out: '2018-03-27 00:00:00+01:00' In : str(paris.normalize(new_time)) Out: '2018-03-27 01:00:00+02:00' 
Si vous avez Python 3.6, la documentation recommande d'utiliser 
dateutil.tz au lieu de 
pytz . Cette bibliothèque est entièrement compatible avec 
tzinfo , elle peut être passée en tant qu'attribut et vous n'avez pas besoin d'utiliser la 
normalize . Certes, cela fonctionne plus lentement.
Si vous voulez savoir pourquoi 
pytz ne prend pas en charge l'API 
datetime ou si vous souhaitez voir plus d'exemples, lisez 
cet article.
Magic StopIteration
Chaque fois que 
next(x) appelé, il retourne une nouvelle valeur de l'itérateur 
x jusqu'à ce qu'une exception soit levée. S'il s'avère que c'est 
StopIteration , alors l'itérateur est épuisé et ne peut plus fournir de valeurs. Si le générateur est itéré, à la fin du corps, il lancera automatiquement 
StopIteration :
 >>> def one_two(): ... yield 1 ... yield 2 ... >>> i = one_two() >>> next(i) 1 >>> next(i) 2 >>> next(i) Traceback (most recent call last): File "<stdin>", line 1, in <module> StopIteration 
StopIteration peut être géré automatiquement par les outils qui appellent 
next :
 >>> list(one_two()) [1, 2] 
Mais le problème est que toute StopIteration manifestement non attendue qui a surgi dans le corps du générateur sera silencieusement prise comme un signe de la fin du générateur, et non comme une erreur, comme toute autre exception:
 def one_two(): yield 1 yield 2 def one_two_repeat(n): for _ in range(n): i = one_two() yield next(i) yield next(i) yield next(i) print(list(one_two_repeat(3))) 
Ici, le dernier 
yield est une erreur: une exception 
StopIteration levée arrête l'itération de la 
list(...) . On obtient le résultat 
[1, 2] . Cependant, dans Python 3.7, ce comportement a changé. Alien 
StopIteration remplacé par 
RuntimeError :
 Traceback (most recent call last): File "test.py", line 10, in one_two_repeat yield next(i) StopIteration The above exception was the direct cause of the following exception: Traceback (most recent call last): File "test.py", line 12, in <module> print(list(one_two_repeat(3))) RuntimeError: generator raised StopIteration 
Vous pouvez utiliser 
__future__ import generator_stop pour activer le même comportement depuis Python 3.5.