Es una nueva selecci贸n de consejos y trucos sobre Python y la programaci贸n de mi canal de Telegram @pythonetc.
Publicaciones anterioresComparaci贸n de estructuras
En ocasiones, desea comparar estructuras complejas en pruebas que ignoran algunos valores. Por lo general, se puede hacer comparando valores particulares con la estructura:
>>> d = dict(a=1, b=2, c=3) >>> assert d['a'] == 1 >>> assert d['c'] == 3
Sin embargo, puede crear un valor especial que informa que es igual a cualquier otro valor:
>>> assert d == dict(a=1, b=ANY, c=3)
Eso se puede hacer f谩cilmente definiendo el m茅todo
__eq__
:
>>> class AnyClass: ... def __eq__(self, another): ... return True ... >>> ANY = AnyClass()
sys.stdout
es un contenedor que le permite escribir cadenas en lugar de bytes sin formato. La cadena se codifica autom谩ticamente mediante
sys.stdout.encoding
:
>>> _ = sys.stdout.write('Stra脽e\n') Stra脽e >>> sys.stdout.encoding 'UTF-8'
sys.stdout.encoding
es de solo lectura y es igual a la codificaci贸n predeterminada de Python, que se puede cambiar configurando la variable de entorno
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 desea escribir bytes en
stdout
, puede omitir la codificaci贸n autom谩tica accediendo al b煤fer envuelto con
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
tambi茅n es un contenedor que almacena en b煤fer por usted. Se puede
sys.stdout.buffer.raw
accediendo al controlador de archivos sin
sys.stdout.buffer.raw
con
sys.stdout.buffer.raw
:
>>> _ = sys.stdout.buffer.raw.write(b'Stra\xc3\x9fe') Stra脽e
Elipsis constante
Python tiene una lista muy corta de constantes integradas. Uno de ellos es
Ellipsis
que tambi茅n se puede escribir como
...
Esta constante no tiene un significado especial para el int茅rprete, pero se usa en lugares donde dicha sintaxis parece apropiada.
numpy
support
Ellipsis
como argumento
__getitem__
, por ejemplo,
x[...]
devuelve todos los elementos de
x
.
PEP 484 define un significado adicional:
Callable[..., type]
es una forma de definir un tipo de invocables sin tipos de argumentos especificados.
Finalmente, puede usar
...
para indicar que la funci贸n a煤n no est谩 implementada. Este es un c贸digo Python completamente v谩lido:
def x(): ...
Sin embargo, en Python 2 Los
Ellipsis
no se pueden escribir como
...
La 煤nica excepci贸n es
a[...]
que significa
a[Ellpsis]
.
Todas las siguientes sintaxis son v谩lidas para Python 3, pero solo la primera l铆nea es v谩lida para Python 2:
a[...] a[...:2:...] [..., ...] {...:...} a = ... ... is ... def a(x=...): ...
Reimportaci贸n de m贸dulos
Los m贸dulos ya importados no se volver谩n a cargar.
import foo
simplemente no hace nada. Sin embargo, result贸 煤til volver a importar m贸dulos mientras se trabaja en un entorno interactivo. La forma correcta de hacer esto en Python 3.4+ es 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
tambi茅n tiene la extensi贸n de
autoreload
que autom谩ticamente
autoreload
importar m贸dulos si es necesario:
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
En algunos idiomas, puede usar la aserci贸n
\G
Coincide en la posici贸n donde finaliza el partido anterior. Eso permite escribir aut贸matas finitos que recorren la cadena palabra por palabra (donde la palabra est谩 definida por la expresi贸n regular).
Sin embargo, no hay tal cosa en Python. La soluci贸n adecuada es rastrear manualmente la posici贸n y pasar la subcadena a las funciones 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))
En el ejemplo anterior, podemos ahorrar algo de tiempo evitando cortar la cadena una y otra vez, pero pidi茅ndole al m贸dulo re que busque desde una posici贸n diferente.
Eso requiere algunos cambios. Primero,
re.search
no admite la b煤squeda desde una posici贸n personalizada, por lo que tenemos que compilar la expresi贸n regular manualmente. En segundo lugar,
^
significa el comienzo real de la cadena, no la posici贸n donde comenz贸 la b煤squeda, por lo que debemos verificar manualmente que la coincidencia ocurri贸 en la misma posici贸n.
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)
Funci贸n redonda
La publicaci贸n de hoy est谩 escrita por orsinium , el autor de @itgram_channel.La funci贸n redondear redondea un n煤mero a una precisi贸n dada en d铆gitos decimales.
>>> round(1.2) 1 >>> round(1.8) 2 >>> round(1.228, 1) 1.2
Tambi茅n puede configurar una precisi贸n negativa:
>>> round(413.77, -1) 410.0 >>> round(413.77, -2) 400.0
round
devuelve el valor del 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 sus propias clases, puede definir el procesamiento redondo con el m茅todo
__round__
:
>>> class Number(int): ... def __round__(self, p=-1000): ... return p ... >>> round(Number(2)) -1000 >>> round(Number(2), -2) -2
Los valores se redondean al m煤ltiplo m谩s cercano de
10 ** (-precision)
. Por ejemplo, para
precision=1
valor se redondear谩 a m煤ltiplo de 0.1:
round(0.63, 1)
devuelve
0.6
. Si dos m煤ltiplos est谩n igualmente cerca, el redondeo se realiza hacia la opci贸n par:
>>> round(0.5) 0 >>> round(1.5) 2
A veces el redondeo de carrozas puede ser un poco sorprendente:
>>> round(2.85, 1) 2.9
Esto se debe a que la mayor铆a de las fracciones decimales no se pueden representar exactamente como un flotante (https://docs.python.org/3.7/tutorial/floatingpoint.html):
>>> format(2.85, '.64f') '2.8500000000000000888178419700125232338905334472656250000000000000'
Si desea redondear a la mitad, puede usar
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')