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