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