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')