Dicas e truques do meu canal Telegram @pythonetc, fevereiro de 2019

imagem

É uma nova seleção de dicas e truques sobre Python e programação do meu canal Telegram @pythonetc.

Publicações anteriores .

Estruturas comparando


Às vezes, você deseja comparar estruturas complexas em testes ignorando alguns valores. Geralmente, isso pode ser feito comparando valores particulares com a estrutura:

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

No entanto, você pode criar um valor especial que relate ser igual a qualquer outro valor:

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

Isso pode ser feito facilmente, definindo o método __eq__ :

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

sys.stdout é um wrapper que permite escrever cadeias de caracteres em vez de bytes brutos. A cadeia é codificada automaticamente usando sys.stdout.encoding :

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

sys.stdout.encoding é somente leitura e é igual à codificação padrão do Python, que pode ser alterada configurando a variável de ambiente 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' 

Se você deseja gravar bytes no stdout pode ignorar a codificação automática acessando o buffer sys.stdout.buffer com sys.stdout.buffer :

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

sys.stdout.buffer também é um wrapper que faz buffer para você. Pode ser ignorado acessando o manipulador de arquivos brutos com sys.stdout.buffer.raw :

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

Constante de reticências


O Python possui uma lista muito curta de constantes internas. Um deles é a Ellipsis que também pode ser escrita como ... Essa constante não tem significado especial para o intérprete, mas é usada em locais onde essa sintaxe parece apropriada.

numpy suporte __getitem__ como um argumento __getitem__ , por exemplo, x[...] retorna todos os elementos de x .

O PEP 484 define significado adicional: Callable[..., type] é uma maneira de definir um tipo de callables sem nenhum tipo de argumento especificado.

Finalmente, você pode usar ... para indicar que a função ainda não está implementada. Este é um código Python completamente válido:

 def x(): ... 

No entanto, no Python 2, a Ellipsis não pode ser escrita como ... A única exceção é a[...] que significa a[Ellpsis] .

Todas as seguintes sintaxes são válidas para o Python 3, mas apenas a primeira linha é válida para o Python 2:

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

Reimportação de módulos


Os módulos já importados não serão carregados novamente. import foo simplesmente não faz nada. No entanto, provou ser útil reimportar módulos enquanto trabalhava em um ambiente interativo. A maneira correta de fazer isso no Python 3.4+ é usar importlib :

 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 

ipython também possui a extensão autoreload que reimporta automaticamente os módulos, se necessário:

 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


Em alguns idiomas, você pode usar a asserção \G Corresponde na posição em que a partida anterior terminou. Isso permite escrever autômatos finitos que percorrem a string palavra por palavra (onde a palavra é definida pelo regex).

No entanto, não existe tal coisa no Python. A solução adequada é rastrear manualmente a posição e passar a substring para as funções regex:

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

No exemplo anterior, podemos economizar algum tempo evitando fatiar a sequência repetidamente, mas solicitando ao re-módulo que procure a partir de uma posição diferente.

Isso requer algumas mudanças. Primeiro, re.search não suporta a pesquisa de uma posição personalizada, portanto, precisamos compilar a expressão regular manualmente. Segundo, ^ significa o início real da sequência, não a posição em que a pesquisa foi iniciada; portanto, temos que verificar manualmente se a correspondência ocorreu na mesma posição.

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

Resultado:

 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) 

Função redonda


O post de hoje foi escrito por orsinium , autor de @itgram_channel.

A função round arredonda um número para uma determinada precisão em dígitos decimais.

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

Além disso, você pode configurar a precisão negativa:

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

round retorna o valor do tipo de número de entrada:

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

Para suas próprias classes, você pode definir o processamento redondo com o método __round__ :

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

Os valores são arredondados para o múltiplo mais próximo de 10 ** (-precision) . Por exemplo, para precision=1 valor será arredondado para múltiplo de 0,1: round(0.63, 1) retorna 0.6 . Se dois múltiplos são igualmente próximos, o arredondamento é feito para a opção par:

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

Às vezes, o arredondamento dos carros alegóricos pode ser um pouco surpreendente:

 >>> round(2.85, 1) 2.9 

Isso ocorre porque a maioria das frações decimais não pode ser representada exatamente como um float (https://docs.python.org/3.7/tutorial/floatingpoint.html):

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

Se você quiser arredondar para metade, use 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/pt444228/


All Articles