Esta es la novena selección de consejos y programación de Python de mi feed @pythonetc.
Selecciones anterioresComparació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')