@Pythonetc août 2018



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' host et le port , 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 # prints 'right' print(Bottom().foo()) 

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.

Source: https://habr.com/ru/post/fr422789/


All Articles