Consejos y trucos de mi canal de Telegram @pythonetc, febrero de 2019

imagen

Es una nueva selecci贸n de consejos y trucos sobre Python y la programaci贸n de mi canal de Telegram @pythonetc.

Publicaciones anteriores

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

Source: https://habr.com/ru/post/444228/


All Articles