@Pythonetc compilation février 2019


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

Sélections précédentes .

Comparaison structurelle


Parfois, lors des tests, il est nécessaire de comparer des structures complexes, en ignorant certaines valeurs. Cela peut généralement être fait en comparant des valeurs spécifiques d'une telle structure:

>>> d = dict(a=1, b=2, c=3) >>> assert d['a'] == 1 >>> assert d['c'] == 3 

Cependant, vous pouvez créer une valeur spéciale qui est égale à toute autre:

 >>> assert d == dict(a=1, b=ANY, c=3) 

Cela se fait facilement en utilisant la méthode magique __eq__ :

 >>> class AnyClass: ... def __eq__(self, another): ... return True ... >>> ANY = AnyClass() 

stdout


sys.stdout est un wrapper qui vous permet d'écrire des valeurs de chaîne, pas d'octets. Ces valeurs de chaîne sont automatiquement encodées à l'aide de sys.stdout.encoding :

 >>> sys.stdout.write('Straße\n') Straße >>> sys.stdout.encoding 'UTF-8' sys.stdout.encoding 

en lecture seule et égal au codage par défaut, qui peut être configuré à l'aide de la PYTHONIOENCODING environnement PYTHONIOENCODING :

 $ PYTHONIOENCODING=cp1251 python3 Python 3.6.6 (default, Aug 13 2018, 18:24:23) [GCC 4.8.5 20150623 (Red Hat 4.8.5-28)] on linux Type "help", "copyright", "credits" or "license" for more information. >>> import sys >>> sys.stdout.encoding 'cp1251' 

Si vous souhaitez écrire des octets dans stdout , vous pouvez ignorer l'encodage automatique en utilisant l' sys.stdout.buffer tampon placé dans le wrapper:

 >>> sys.stdout <_io.TextIOWrapper name='<stdut>' mode='w' encoding='cp1251'> >>> sys.stdout.buffer <_io.BufferedWriter name='<stdut>'> >>> sys.stdout.buffer.write(b'Stra\xc3\x9fe\n') Straße sys.stdout.buffer 

également un emballage. Il peut être contourné en contactant le descripteur de fichier avec sys.stdout.buffer.raw :

 >>> sys.stdout.buffer.raw.write(b'Stra\xc3\x9fe') Straße 

Ellipses constantes


Python a très peu de constantes intégrées. L'un d'eux, Ellipsis , peut également s'écrire ... Pour l'interpréteur, cette constante n'a pas de valeur spécifique, mais elle est utilisée lorsque cette syntaxe est appropriée.

numpy prend en charge Ellipsis comme argument pour __getitem__ , par exemple, x[...] renvoie tous les éléments de x .

PEP 484 définit une autre valeur pour cette constante: Callable[..., type] vous permet de déterminer les types d'appels sans spécifier les types d'arguments.

Enfin, vous pouvez utiliser ... pour indiquer qu'une fonction n'a pas encore été implémentée. C'est du code Python complètement correct:

 def x(): ... 

Cependant, en Python 2, les Ellipsis ne peuvent pas être écrits comme ... La seule exception est a[...] , qui est interprété comme a[Ellipsis] .

Cette syntaxe est correcte pour Python 3, mais seule la première ligne est correcte pour Python 2:

 a[...] a[...:2:...] [..., ...] {...:...} a = ... ... is ... def a(x=...): ... 

Réimporter des modules


Les modules déjà importés ne se chargeront plus. La commande import foo ne fait tout simplement rien. Cependant, il est utile pour réimporter des modules lorsque vous travaillez dans un environnement interactif. Dans Python 3.4+, importlib doit être utilisé pour cela:

 In [1]: import importlib In [2]: with open('foo.py', 'w') as f: ...: f.write('a = 1') ...: In [3]: import foo In [4]: foo.a Out[4]: 1 In [5]: with open('foo.py', 'w') as f: ...: f.write('a = 2') ...: In [6]: foo.a Out[6]: 1 In [7]: import foo In [8]: foo.a Out[8]: 1 In [9]: importlib.reload(foo) Out[9]: <module 'foo' from '/home/v.pushtaev/foo.py'> In [10]: foo.a Out[10]: 2 

Il ipython également une extension de ipython automatique pour ipython , qui réimportera automatiquement les modules si nécessaire:

 In [1]: %load_ext autoreload In [2]: %autoreload 2 In [3]: with open('foo.py', 'w') as f: ...: f.write('print("LOADED"); a=1') ...: In [4]: import foo LOADED In [5]: foo.a Out[5]: 1 In [6]: with open('foo.py', 'w') as f: ...: f.write('print("LOADED"); a=2') ...: In [7]: import foo LOADED In [8]: foo.a Out[8]: 2 In [9]: with open('foo.py', 'w') as f: ...: f.write('print("LOADED"); a=3') ...: In [10]: foo.a LOADED Out[10]: 3 

\ G


Dans certaines langues, vous pouvez utiliser l'expression \G Il recherche une correspondance à partir de la position à laquelle la recherche précédente s'est terminée. Cela nous permet d'écrire des machines à états finis qui traitent les valeurs de chaîne mot par mot (le mot est déterminé par une expression régulière).

En Python, il n'y a rien de tel que cette expression, et vous pouvez implémenter des fonctionnalités similaires en suivant manuellement la position et en passant une partie de la chaîne aux fonctions d'expression régulière:

 import re import json text = '<a><b>foo</b><c>bar</c></a><z>bar</z>' regex = '^(?:<([az]+)>|</([az]+)>|([az]+))' stack = [] tree = [] pos = 0 while len(text) > pos: error = f'Error at {text[pos:]}' found = re.search(regex, text[pos:]) assert found, error pos += len(found[0]) start, stop, data = found.groups() if start: tree.append(dict( tag=start, children=[], )) stack.append(tree) tree = tree[-1]['children'] elif stop: tree = stack.pop() assert tree[-1]['tag'] == stop, error if not tree[-1]['children']: tree[-1].pop('children') elif data: stack[-1][-1]['data'] = data print(json.dumps(tree, indent=4)) 

Dans l'exemple ci-dessus, vous pouvez gagner du temps sur le traitement sans interrompre la ligne encore et encore, et demander au module re de commencer la recherche à partir d'une position différente.

Pour ce faire, vous devez apporter quelques modifications au code. Tout d'abord, re.search ne prend pas en charge la détermination de la position du début de la recherche, vous devez donc compiler manuellement l'expression régulière. Deuxièmement, ^ indique le début de la valeur de la chaîne, pas la position du début de la recherche, vous devez donc vérifier manuellement que la correspondance se trouve à la même position.

 import re import json text = '<a><b>foo</b><c>bar</c></a><z>bar</z>' * 10 def print_tree(tree): print(json.dumps(tree, indent=4)) def xml_to_tree_slow(text): regex = '^(?:<([az]+)>|</([az]+)>|([az]+))' stack = [] tree = [] pos = 0 while len(text) > pos: error = f'Error at {text[pos:]}' found = re.search(regex, text[pos:]) assert found, error pos += len(found[0]) start, stop, data = found.groups() if start: tree.append(dict( tag=start, children=[], )) stack.append(tree) tree = tree[-1]['children'] elif stop: tree = stack.pop() assert tree[-1]['tag'] == stop, error if not tree[-1]['children']: tree[-1].pop('children') elif data: stack[-1][-1]['data'] = data def xml_to_tree_slow(text): regex = '^(?:<([az]+)>|</([az]+)>|([az]+))' stack = [] tree = [] pos = 0 while len(text) > pos: error = f'Error at {text[pos:]}' found = re.search(regex, text[pos:]) assert found, error pos += len(found[0]) start, stop, data = found.groups() if start: tree.append(dict( tag=start, children=[], )) stack.append(tree) tree = tree[-1]['children'] elif stop: tree = stack.pop() assert tree[-1]['tag'] == stop, error if not tree[-1]['children']: tree[-1].pop('children') elif data: stack[-1][-1]['data'] = data return tree _regex = re.compile('(?:<([az]+)>|</([az]+)>|([az]+))') def _error_message(text, pos): return text[pos:] def xml_to_tree_fast(text): stack = [] tree = [] pos = 0 while len(text) > pos: error = f'Error at {text[pos:]}' found = _regex.search(text, pos=pos) begin, end = found.span(0) assert begin == pos, _error_message(text, pos) assert found, _error_message(text, pos) pos += len(found[0]) start, stop, data = found.groups() if start: tree.append(dict( tag=start, children=[], )) stack.append(tree) tree = tree[-1]['children'] elif stop: tree = stack.pop() assert tree[-1]['tag'] == stop, _error_message(text, pos) if not tree[-1]['children']: tree[-1].pop('children') elif data: stack[-1][-1]['data'] = data return tree print_tree(xml_to_tree_fast(text)) 

Résultats:

 In [1]: from example import * In [2]: %timeit xml_to_tree_slow(text) 356 µs ± 16.9 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each) In [3]: %timeit xml_to_tree_fast(text) 294 µs ± 6.15 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each) 

Arrondir les nombres


Cet article a été écrit par orsinium , auteur de la chaîne Telegram @itgram_channel.

La fonction d' round arrondit un nombre au nombre de décimales spécifié.

 >>> round(1.2) 1 >>> round(1.8) 2 >>> round(1.228, 1) 1.2 

Vous pouvez également définir une précision d'arrondi négative:

 >>> round(413.77, -1) 410.0 >>> round(413.77, -2) 400.0 round 

renvoie une valeur du même type que le numéro d'entrée:

 >>> type(round(2, 1)) <class 'int'> >>> type(round(2.0, 1)) <class 'float'> >>> type(round(Decimal(2), 1)) <class 'decimal.Decimal'> >>> type(round(Fraction(2), 1)) <class 'fractions.Fraction'> 

Pour vos propres classes, vous pouvez définir un traitement round à l'aide de la méthode __round__ :

 >>> class Number(int): ... def __round__(self, p=-1000): ... return p ... >>> round(Number(2)) -1000 >>> round(Number(2), -2) -2 

Ici, les valeurs sont arrondies au multiple de 10 ** (-precision) le plus proche 10 ** (-precision) . Par exemple, avec une precision=1 valeur sera arrondie à un multiple de 0,1: round(0.63, 1) renvoie 0.6 . Si deux nombres multiples sont également proches, l'arrondi est effectué en un nombre pair:

 >>> round(0.5) 0 >>> round(1.5) 2 

Parfois, arrondir un nombre à virgule flottante peut donner un résultat inattendu:

 >>> round(2.85, 1) 2.9 

Le fait est que la plupart des fractions décimales ne peuvent pas être exprimées avec précision en utilisant un nombre à virgule flottante ( https://docs.python.org/3.7/tutorial/floatingpoint.html ):

 >>> format(2.85, '.64f') '2.8500000000000000888178419700125232338905334472656250000000000000' 

Si vous souhaitez arrondir les moitiés vers le haut, utilisez decimal.Decimal .

 >>> from decimal import Decimal, ROUND_HALF_UP >>> Decimal(1.5).quantize(0, ROUND_HALF_UP) Decimal('2') >>> Decimal(2.85).quantize(Decimal('1.0'), ROUND_HALF_UP) Decimal('2.9') >>> Decimal(2.84).quantize(Decimal('1.0'), ROUND_HALF_UP) Decimal('2.8') 

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


All Articles