@Pythonetc compilación febrero 2019


Esta es la novena selección de consejos y programación de Python de mi feed @pythonetc.

Selecciones anteriores

Comparación estructural


A veces, cuando se realiza la prueba, es necesario comparar estructuras complejas, ignorando algunos valores. Esto generalmente se puede hacer comparando valores específicos de dicha 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 sea igual a cualquier otro:

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

Esto se hace fácilmente usando el método mágico __eq__ :

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

stdout


sys.stdout es un contenedor que le permite escribir valores de cadena, no bytes. Estos valores de cadena se codifican automáticamente mediante sys.stdout.encoding :

 >>> sys.stdout.write('Straße\n') Straße >>> sys.stdout.encoding 'UTF-8' sys.stdout.encoding 

solo lectura e igual a la codificación predeterminada, que se puede configurar utilizando la PYTHONIOENCODING 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 utilizando la sys.stdout.buffer búfer ubicado en el contenedor:

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

También una envoltura. Se puede sys.stdout.buffer.raw contactando el descriptor de archivo con sys.stdout.buffer.raw :

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

Puntos suspensivos constantes


Python tiene muy pocas constantes incorporadas. Uno de ellos, Ellipsis , también se puede escribir como ... Para el intérprete, esta constante no tiene ningún valor específico, pero se usa donde dicha sintaxis es apropiada.

numpy admite numpy Ellipsis como argumento para __getitem__ , por ejemplo, x[...] devuelve todos los elementos de x .

PEP 484 define otro valor para esta constante: Callable[..., type] permite determinar los tipos de los llamados sin especificar los tipos de argumentos.

Finalmente, puede usar ... para indicar que una función aún no se ha implementado. Este es el código Python completamente correcto:

 def x(): ... 

Sin embargo, en Python 2 Los Ellipsis no se pueden escribir como ... La única excepción es a[...] , que se interpreta como a[Ellipsis] .

Esta sintaxis es correcta para Python 3, pero solo la primera línea es correcta para Python 2:

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

Reimportar módulos


Los módulos ya importados no se cargarán nuevamente. El comando import foo simplemente no hace nada. Sin embargo, es útil para volver a importar módulos cuando se trabaja en un entorno interactivo. En Python 3.4+, importlib debe usarse para esto:

 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 

También hay una extensión de ipython automática para ipython , que reimportará automáticamente los 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 expresión \G Busca una coincidencia desde la posición en la que finalizó la búsqueda anterior. Esto nos permite escribir máquinas de estados finitos que procesan valores de cadena palabra por palabra (la palabra está determinada por una expresión regular).

En Python, no hay nada como esta expresión, y puede implementar una funcionalidad similar al rastrear manualmente la posición y pasar parte de la cadena a las funciones de expresión 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)) 

En el ejemplo anterior, puede ahorrar tiempo en el procesamiento sin romper la línea una y otra vez, y pedirle al módulo re que comience a buscar desde otra posición.

Para hacer esto, debe realizar algunos cambios en el código. En primer lugar, re.search no admite la determinación de la posición del comienzo de la búsqueda, por lo que debe compilar la expresión regular manualmente. En segundo lugar, ^ denota el comienzo del valor de la cadena, no la posición del inicio de la búsqueda, por lo que debe verificar manualmente que la coincidencia se encuentre 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)) 

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 redondeo


Este artículo fue escrito por orsinium , autor del canal Telegram @itgram_channel.

La función redondear redondea un número al número especificado de lugares decimales.

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

También puede establecer una precisión de redondeo negativa:

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

devuelve un valor del mismo tipo que el 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 round utilizando el método __round__ :

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

Aquí los valores se redondean al múltiplo más cercano de 10 ** (-precision) . Por ejemplo, con precision=1 valor se redondeará a un múltiplo de 0.1: round(0.63, 1) devuelve 0.6 . Si dos números múltiples están igualmente cerca, entonces el redondeo se realiza a un número par:

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

A veces, redondear un número de coma flotante puede dar un resultado inesperado:

 >>> round(2.85, 1) 2.9 

El hecho es que la mayoría de las fracciones decimales no se pueden expresar con precisión utilizando un número de coma flotante ( https://docs.python.org/3.7/tutorial/floatingpoint.html ):

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

Si desea redondear mitades hacia arriba, use decimal.Decimal . 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/444226/


All Articles