@Pythonetc septembre 2018


Ceci est la quatrième collection de conseils et de programmation Python de mon flux @pythonetc .


Sélections précédentes:



Remplacement et surcharge


Il y a deux concepts faciles à confondre: le remplacement et la surcharge.


La substitution se produit lorsqu'une classe enfant définit une méthode déjà fournie par les classes parentes et la remplace ainsi. Dans certains langages, il est nécessaire de marquer explicitement la méthode de override (en C #, le modificateur de override est utilisé), et dans certains langages, cela se fait comme souhaité (annotation @Override en Java). Python ne nécessite pas l'utilisation d'un modificateur spécial et ne fournit pas de marquage standard de ces méthodes (pour des raisons de lisibilité, quelqu'un utilise le décorateur @override personnalisé, qui ne fait rien).


La surcharge est une autre histoire. Ce terme fait référence à une situation où plusieurs fonctions portent le même nom, mais avec des signatures différentes. La surcharge est possible en Java et C ++; elle est souvent utilisée pour fournir des arguments par défaut:


 class Foo { public static void main(String[] args) { System.out.println(Hello()); } public static String Hello() { return Hello("world"); } public static String Hello(String name) { return "Hello, " + name; } } 

Python ne prend pas en charge la recherche de fonctions par signature, uniquement par nom. Bien sûr, vous pouvez écrire du code qui analyse explicitement les types et le nombre d'arguments, mais cela semblera gênant, et cette pratique est préférable d'éviter:


 def quadrilateral_area(*args): if len(args) == 4: quadrilateral = Quadrilateral(*args) elif len(args) == 1: quadrilateral = args[0] else: raise TypeError() return quadrilateral.area() 

Si vous avez besoin d'indices de typing , utilisez le module de typing avec le décorateur @overload :


 from typing import overload @overload def quadrilateral_area( q: Quadrilateral ) -> float: ... @overload def quadrilateral_area( p1: Point, p2: Point, p3: Point, p4: Point ) -> float: ... 

Vivification automatique


collections.defaultdict vous permet de créer un dictionnaire qui renvoie une valeur par défaut si la clé demandée est manquante (au lieu de lancer une KeyError ). Pour créer un defaultdict vous devez fournir non seulement une valeur par défaut, mais une fabrique de ces valeurs.


Vous pouvez donc créer un dictionnaire avec un nombre pratiquement infini de dictionnaires imbriqués, ce qui vous permet d'utiliser des constructions comme d[a][b][c]...[z] .


 >>> def infinite_dict(): ... return defaultdict(infinite_dict) ... >>> d = infinite_dict() >>> d[1][2][3][4] = 10 >>> dict(d[1][2][3][5]) {} 

Ce comportement est appelé «auto-vivification», un terme qui vient de Perl.


Instanciation


L'instanciation d'objets implique deux étapes importantes. Tout d'abord, la méthode __new__ est appelée à partir de la classe, qui crée et renvoie un nouvel objet. Python appelle ensuite la méthode __init__ , qui définit l'état initial de cet objet.


Cependant, __init__ ne sera pas appelé si __new__ renvoie un objet qui n'est pas une instance de la classe d'origine. Dans ce cas, l'objet pourrait être créé par une autre classe, ce qui signifie que __init__ a déjà été appelé sur l'objet:


 class Foo: def __new__(cls, x): return dict(x=x) def __init__(self, x): print(x) # Never called print(Foo(0)) 

Cela signifie également que vous ne devez pas instancier la même classe dans __new__ utilisant le constructeur normal ( Foo(...) ). Cela peut conduire à une exécution répétée de __init__ , voire à une récursion infinie.


Récursion infinie:


 class Foo: def __new__(cls, x): return Foo(-x) # Recursion 

Double exécution __init__ :


 class Foo: def __new__(cls, x): if x < 0: return Foo(-x) return super().__new__(cls) def __init__(self, x): print(x) self._x = x 

La bonne façon:


 class Foo: def __new__(cls, x): if x < 0: return cls.__new__(cls, -x) return super().__new__(cls) def __init__(self, x): print(x) self._x = x 

L'opérateur [] et les tranches


En Python, vous pouvez remplacer l'opérateur [] en définissant la méthode magique __getitem__ . Ainsi, par exemple, vous pouvez créer un objet qui contient virtuellement un nombre infini d'éléments répétitifs:


 class Cycle: def __init__(self, lst): self._lst = lst def __getitem__(self, index): return self._lst[ index % len(self._lst) ] print(Cycle(['a', 'b', 'c'])[100]) # 'b' 

La chose inhabituelle ici est que l'opérateur [] prend en charge une syntaxe unique. En l'utilisant, vous pouvez obtenir non seulement [2] , mais aussi [2:10] , [2:10:2] , [2::2] et même [:] . La sémantique de l'opérateur est: [start: stop: step], mais vous pouvez l'utiliser de toute autre manière pour créer des objets personnalisés.


Mais si vous appelez __getitem__ avec cette syntaxe, __getitem__ t-il comme paramètre d'index? C'est exactement pourquoi les objets slice existent.


 In : class Inspector: ...: def __getitem__(self, index): ...: print(index) ...: In : Inspector()[1] 1 In : Inspector()[1:2] slice(1, 2, None) In : Inspector()[1:2:3] slice(1, 2, 3) In : Inspector()[:] slice(None, None, None) 

Vous pouvez même combiner la syntaxe des tuples et des tranches:


 In : Inspector()[:, 0, :] (slice(None, None, None), 0, slice(None, None, None)) 

slice ne fait rien, ne stocke que les attributs start , stop et step .


 In : s = slice(1, 2, 3) In : s.start Out: 1 In : s.stop Out: 2 In : s.step Out: 3 

Interrompre Asyncio Coroutine


Tout asyncio coroutine exécutable peut être abandonné à l'aide de la méthode cancel() . Dans ce cas, une CanceledError sera envoyée à la coroutine, en conséquence, celle-ci et toutes les coroutines qui lui sont associées seront interrompues jusqu'à ce que l'erreur soit détectée et supprimée.


CancelledError est une sous-classe d' Exception , ce qui signifie qu'elle peut être accidentellement interceptée en utilisant une combinaison de try ... except Exception , conçue pour intercepter "toutes les erreurs". Pour attraper en toute sécurité un bug pour une coroutine, vous devez faire ceci:


 try: await action() except asyncio.CancelledError: raise except Exception: logging.exception('action failed') 

Planification de l'exécution


Pour planifier l'exécution de certains codes à un certain moment, asyncio crée généralement une tâche qui s'exécute await asyncio.sleep(x) :


 import asyncio async def do(n=0): print(n) await asyncio.sleep(1) loop.create_task(do(n + 1)) loop.create_task(do(n + 1)) loop = asyncio.get_event_loop() loop.create_task(do()) loop.run_forever() 

Mais la création d'une nouvelle tâche peut coûter cher, et il n'est pas nécessaire de le faire si vous ne prévoyez pas d'effectuer des opérations asynchrones (comme la fonction do dans mon exemple). Au lieu de cela, vous pouvez utiliser les fonctions loop.call_later et loop.call_at , qui vous permettent de planifier un appel de rappel asynchrone:


 import asyncio def do(n=0): print(n) loop = asyncio.get_event_loop() loop.call_later(1, do, n+1) loop.call_later(1, do, n+1) loop = asyncio.get_event_loop() do() loop.run_forever() 

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


All Articles