@Pythonetc compilation fevereiro 2019


Esta é a nona seleção de dicas e programação em Python no meu feed @pythonetc.

Seleções anteriores .

Comparação Estrutural


Às vezes, ao testar, é necessário comparar estruturas complexas, ignorando alguns valores. Isso geralmente pode ser feito comparando valores específicos de uma 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 igual a qualquer outro:

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

Isso é feito facilmente usando o método mágico __eq__ :

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

stdout


O sys.stdout é um wrapper que permite escrever valores de sequência, não bytes. Esses valores de sequência são codificados 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, que pode ser configurada usando a PYTHONIOENCODING 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 pular a codificação automática usando o sys.stdout.buffer buffer colocado no 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 

também um invólucro. Pode ser ignorado entrando em contato com o descritor de arquivo com sys.stdout.buffer.raw :

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

Elipse constante


Python tem muito poucas constantes embutidas. Um deles, Ellipsis , também pode ser escrito como ... Para o intérprete, essa constante não possui nenhum valor específico, mas é usada onde essa sintaxe é apropriada.

numpy suporta Ellipsis como argumento para __getitem__ , por exemplo, x[...] retorna todos os elementos de x .

O PEP 484 define outro valor para esta constante: Callable[..., type] permite determinar os tipos dos chamados sem especificar os tipos de argumentos.

Finalmente, você pode usar ... para indicar que uma função ainda não foi implementada. Este é o código Python completamente correto:

 def x(): ... 

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

Essa sintaxe está correta no Python 3, mas apenas a primeira linha está correta no Python 2:

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

Reimporte módulos


Os módulos já importados não serão carregados novamente. O comando import foo simplesmente não faz nada. No entanto, é útil para reimportar módulos ao trabalhar em um ambiente interativo. No Python 3.4+, o importlib deve ser usado para isso:

 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 

Há também uma extensão de ipython automático para ipython , 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 expressão \G Ele procura uma correspondência na posição em que a pesquisa anterior terminou. Isso nos permite escrever máquinas de estados finitos que processam valores de string palavra por palavra (a palavra é determinada por uma expressão regular).

No Python, não há nada como essa expressão, e você pode implementar funcionalidades semelhantes rastreando manualmente a posição e passando parte da string para as funções de expressão regular:

 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 acima, você pode economizar tempo no processamento sem interromper a linha repetidamente e solicitar ao módulo remotamente que comece a procurar em outra posição.

Para fazer isso, você precisa fazer algumas alterações no código. Em primeiro lugar, re.search não suporta determinar a posição do início da pesquisa, portanto, você deve compilar a expressão regular manualmente. Em segundo lugar, ^ denota o início do valor da sequência, e não a posição do início da pesquisa, portanto, é necessário verificar manualmente se a correspondência foi encontrada 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)) 

Resultados:

 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) 

Números de arredondamento


Este item foi escrito por orsinium , autor do canal Telegram @itgram_channel.

A função round arredonda um número para o número especificado de casas decimais.

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

Você também pode definir uma precisão de arredondamento negativa:

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

retorna um valor do mesmo tipo que o 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 round usando o método __round__ :

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

Aqui, os valores são arredondados para o múltiplo mais próximo de 10 ** (-precision) . Por exemplo, com precision=1 valor será arredondado para um múltiplo de 0,1: round(0.63, 1) retorna 0.6 . Se dois números múltiplos estiverem igualmente próximos, o arredondamento será realizado para um número par:

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

Às vezes, arredondar um número de ponto flutuante pode gerar um resultado inesperado:

 >>> round(2.85, 1) 2.9 

O fato é que a maioria das frações decimais não pode ser expressa com precisão usando um número de ponto flutuante ( https://docs.python.org/3.7/tutorial/floatingpoint.html ):

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

Se você quiser arredondar as metades para cima, 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/pt444226/


All Articles