Conseils Python utiles que vous n'avez pas rencontrés

De nombreux articles ont été écrits sur des fonctionnalités intéressantes de Python. Ils parlent de décompresser des listes et des tuples en variables, d'appliquer partiellement des fonctions, de travailler avec des objets itérables. Mais en Python, il y a tellement plus. L'auteur de l'article que nous traduisons aujourd'hui dit qu'il veut parler de certaines des fonctionnalités Python qu'il utilise. En même temps, il n'a pas encore rencontré de description de ces possibilités, similaire à celle donnée ici. Il est possible que vous n'en ayez pas lu ailleurs.



Effacement des données de chaîne d'entrée


La tâche de nettoyage des données saisies par l'utilisateur est pertinente pour presque tous les programmes. Souvent, ce traitement d'entrée se résume à convertir des caractères en majuscules ou en minuscules. Parfois, les données peuvent être effacées à l'aide d'une expression régulière. Mais dans les cas où la tâche est compliquée, vous pouvez appliquer une méthode plus efficace pour la résoudre. Par exemple - ceci:

user_input = "This\nstring has\tsome whitespaces...\r\n" character_map = {  ord('\n') : ' ',  ord('\t') : ' ',  ord('\r') : None } user_input.translate(character_map) # This string has some whitespaces... " 

Ici, vous pouvez voir comment les caractères blancs "\n" et "\t" sont remplacés par des espaces normaux et comment le caractère "\r" est complètement supprimé de la chaîne. Il s'agit d'un exemple simple, mais nous pouvons l'étendre en créant de grandes tables de remappage de caractères à l'aide du package unicodedata et de sa fonction combining() . Cette approche vous permet de supprimer des lignes tout ce qui n'y est pas nécessaire.

Obtention de tranches d'itérateur


Si vous essayez d'obtenir une tranche de l'itérateur, vous rencontrerez une erreur TypeError , qui indique que vous ne pouvez pas vous abonner à l'objet générateur. Cependant, ce problème peut être résolu:

 import itertools s = itertools.islice(range(50), 10, 20) # <itertools.islice object at 0x7f70fab88138> for val in s: ... 

En utilisant la méthode itertools.islice , itertools.islice pouvez créer un objet islice , qui est un itérateur qui islice éléments nécessaires. Cependant, il est important de noter ici que cette construction utilise tous les éléments du générateur jusqu'au début de la tranche et tous les éléments de l'objet islice .

Ignorer le début de l'objet itérable


Parfois, vous devez travailler avec un fichier qui, comme vous le savez, commence par un certain nombre de lignes inutiles - comme des lignes avec des commentaires. Afin de sauter ces lignes, vous pouvez à nouveau recourir aux itertools :

 string_from_file = """ // Author: ... // License: ... // // Date: ... Actual content... """ import itertools for line in itertools.dropwhile(lambda line: line.startswith("//"), string_from_file.split("\n")): print(line) 

Ce code renvoie uniquement des lignes après le bloc de commentaires situé au début du fichier. Une telle approche peut être utile lorsque vous devez supprimer uniquement les éléments (dans notre cas, les lignes) au début de l'objet itérable, mais leur nombre exact n'est pas connu.

Fonctions prenant en charge uniquement les arguments nommés (kwargs)


Afin de le rendre possible lors de l'utilisation d'une certaine fonction afin que seuls les arguments nommés puissent lui être transmis, vous pouvez effectuer les opérations suivantes:

 def test(*, a, b): pass test("value for a", "value for b") # TypeError: test() takes 0 positional arguments... test(a="value", b="value 2") #   - ... 

Cela peut être utile pour améliorer la compréhensibilité du code. Comme vous pouvez le voir, notre problème est facilement résolu en utilisant l'argument * devant la liste des arguments nommés. Ici, ce qui est assez évident, vous pouvez également utiliser des arguments positionnels - si vous les placez avant l'argument * .

Création d'objets prenant en charge l'instruction with


Tout le monde sait, par exemple, comment ouvrir un fichier ou, éventuellement, comment définir un verrou à l'aide de l'instruction with . Mais est-il possible de mettre en œuvre indépendamment le mécanisme de contrôle de verrouillage? Oui, c'est bien réel. Le protocole de gestion du contexte d'exécution est implémenté à l'aide des méthodes __enter__ et __exit__ :

 class Connection: def __init__(self):  ... def __enter__(self):  #  ... def __exit__(self, type, value, traceback):  #  ... with Connection() as c: # __enter__() executes ... # conn.__exit__() executes 

C'est la façon la plus courante d'implémenter les capacités du gestionnaire de contexte en Python, mais la même chose peut être faite plus facilement:

 from contextlib import contextmanager @contextmanager def tag(name): print(f"<{name}>") yield print(f"</{name}>") with tag("h1"): print("This is Title.") 

Ici, le protocole de gestion de contexte est implémenté à l'aide du décorateur contextmanager . La première partie de la fonction tag (avant yield ) est exécutée lorsque vous entrez dans le bloc with . Ce bloc est ensuite exécuté, puis le reste de la fonction de tag est exécuté.

Économisez de la mémoire avec __slots__


Si vous avez déjà écrit des programmes qui créent un très grand nombre d'instances d'une certaine classe, vous remarquerez peut-être que ces programmes peuvent nécessiter de manière inattendue beaucoup de mémoire. En effet, Python utilise des dictionnaires pour représenter les attributs des instances de classe. Cela a un bon effet sur les performances, mais, en termes de consommation de mémoire, il est inefficace. Cependant, cette fonctionnalité ne pose généralement pas de problèmes. Cependant, si vous êtes confronté à un manque de mémoire dans une situation similaire, vous pouvez essayer d'utiliser l'attribut __slots__ :

 class Person: __slots__ = ["first_name", "last_name", "phone"] def __init__(self, first_name, last_name, phone):  self.first_name = first_name  self.last_name = last_name  self.phone = phone 

Ici, lorsque nous déclarons l'attribut __slots__ , Python utilise un petit tableau d'une taille fixe pour stocker les attributs, pas un dictionnaire. Cela réduit considérablement la quantité de mémoire requise pour chaque instance de la classe. L'utilisation de l'attribut __slots__ présente certains inconvénients. Donc, en l'utilisant, nous ne pouvons pas déclarer de nouveaux attributs, nous sommes limités uniquement à ceux qui sont dans __slots__ . De plus, les classes avec l'attribut __slots__ ne peuvent pas utiliser l'héritage multiple.

Limites du processeur et de la mémoire


Si, au lieu d'optimiser le programme ou d'améliorer la façon dont il utilise le processeur, vous avez juste besoin de définir une restriction stricte sur les ressources disponibles, vous pouvez utiliser la bibliothèque appropriée:

 import signal import resource import os #     def time_exceeded(signo, frame): print("CPU exceeded...") raise SystemExit(1) def set_max_runtime(seconds): #   signal     soft, hard = resource.getrlimit(resource.RLIMIT_CPU) resource.setrlimit(resource.RLIMIT_CPU, (seconds, hard)) signal.signal(signal.SIGXCPU, time_exceeded) #     def set_max_memory(size): soft, hard = resource.getrlimit(resource.RLIMIT_AS) resource.setrlimit(resource.RLIMIT_AS, (size, hard)) 

Cela montre la limitation du temps processeur et de la taille de la mémoire. Afin de limiter l’utilisation du processeur par le programme, nous obtenons d’abord les valeurs des limites non-hard (soft) et hard (hard) pour une ressource particulière ( RLIMIT_CPU ). Ensuite, nous définissons la limite en utilisant un certain nombre de secondes spécifié par l'argument seconds et la valeur de limite ferme précédemment obtenue. Après cela, nous enregistrons le gestionnaire de signal qui, lorsque le temps processeur alloué au programme est dépassé, lance la procédure de sortie. Dans le cas de la mémoire, nous obtenons à nouveau des valeurs pour les limites non rigides et dures, après quoi nous définissons la limite en utilisant la méthode setrlimit , à laquelle nous passons la taille de la contrainte ( size ) et la valeur précédemment obtenue de la limite dure.

Contrôler ce qui peut être importé du module et ce qui ne peut pas


Certaines langues ont des mécanismes d'exportation extrêmement clairs à partir de modules de variables, de méthodes et d'interfaces. Par exemple, seules les entités dont le nom commence par une majuscule sont exportées vers Golang. En Python, tout est exporté. Mais seulement jusqu'à ce que l'attribut __all__ soit __all__ :

 def foo(): pass def bar(): pass __all__ = ["bar"] 

Dans l'exemple ci-dessus, seule la fonction de bar sera exportée. Et si vous laissez l'attribut __all__ vide, alors rien ne sera exporté du module. Tenter d'importer quelque chose à partir d'un tel module générera une erreur AttributeError .

Simplifiez la création d'opérateurs de comparaison


Il existe de nombreux opérateurs de comparaison. Par exemple, __lt__ , __le__ , __gt__ , __ge__ . Peu de gens apprécieront la perspective de leur mise en œuvre pour une certaine classe. Existe-t-il un moyen de simplifier cette tâche ennuyeuse? Oui, vous pouvez - avec l'aide du décorateur functools.total_ordering :

 from functools import total_ordering @total_ordering class Number: def __init__(self, value):  self.value = value def __lt__(self, other):  return self.value < other.value def __eq__(self, other):  return self.value == other.value print(Number(20) > Number(3)) print(Number(1) < Number(5)) print(Number(15) >= Number(15)) print(Number(10) <= Number(2)) 

Le décorateur functools.total_ordering utilisé ici pour simplifier le processus d'implémentation de l'ordre des instances de classe. Pour assurer son fonctionnement, il suffit de __lt__ opérateurs de comparaison __lt__ et __eq__ . C'est le minimum dont un décorateur a besoin pour construire les opérateurs de comparaison restants.

Résumé


Cela ne veut pas dire que tout ce dont j'ai parlé ici est absolument nécessaire dans le travail quotidien de chaque programmeur Python. Mais certaines des techniques présentées ici, de temps en temps, peuvent être très utiles. De plus, ils sont capables de simplifier la résolution des problèmes, pour la solution habituelle dont elle peut nécessiter beaucoup de code et une grande quantité de travail uniforme. De plus, je voudrais noter que tout ce qui a été discuté fait partie de la bibliothèque standard Python. Pour être honnête, certaines de ces fonctionnalités semblent quelque peu inattendues pour la bibliothèque standard. Cela suggère que si quelqu'un va implémenter en Python quelque chose qui ne semble pas tout à fait ordinaire, il devrait d'abord fouiller dans la bibliothèque standard. Si vous ne trouvez pas immédiatement ce dont vous avez besoin, alors peut-être que cela en vaut la peine, très soigneusement, de creuser là-bas. Certes, si une recherche approfondie n'a pas réussi, alors, très probablement, ce dont vous avez besoin n'est vraiment pas là. Et si oui, alors vous devriez vous tourner vers des bibliothèques tierces. En eux, on peut certainement le trouver.

Chers lecteurs! Connaissez-vous des fonctionnalités Python standard qui pourraient sembler plutôt inhabituelles à première vue pour être appelées «standard»?


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


All Articles