Se han escrito muchos artículos sobre características interesantes de Python. Hablan sobre desempaquetar listas y tuplas en variables, sobre la aplicación parcial de funciones, sobre trabajar con objetos iterables. Pero en Python hay mucho más. El autor del artículo que estamos traduciendo hoy dice que quiere hablar sobre algunas de las características de Python que usa. Al mismo tiempo, aún no ha encontrado una descripción de estas posibilidades, similar a la que se ofrece aquí. Es posible que no hayas leído sobre ellos en ningún otro lado.

Borrar datos de cadena de entrada
La tarea de limpiar los datos ingresados por el usuario es relevante para casi cualquier programa. A menudo, este procesamiento de entrada se reduce a convertir caracteres en mayúsculas o minúsculas. A veces, los datos se pueden borrar utilizando una expresión regular. Pero en los casos en que la tarea es complicada, puede aplicar una forma más exitosa de resolverla. Por ejemplo, esto:
user_input = "This\nstring has\tsome whitespaces...\r\n" character_map = { ord('\n') : ' ', ord('\t') : ' ', ord('\r') : None } user_input.translate(character_map)
Aquí puede ver cómo los caracteres de espacio en blanco
"\n"
y
"\t"
se reemplazan con espacios regulares, y cómo el carácter
"\r"
se elimina por completo de la cadena. Este es un ejemplo simple, pero podemos extenderlo creando grandes tablas de reasignación de caracteres usando el paquete
unicodedata
y su función de
combining()
. Este enfoque le permite eliminar de las líneas todo lo que no se necesita allí.
Obteniendo rebanadas de iterador
Si intenta obtener una porción del iterador, encontrará un error
TypeError
, que indica que no puede suscribirse al objeto generador. Sin embargo, este problema se puede resolver:
import itertools s = itertools.islice(range(50), 10, 20)
Usando el método
itertools.islice
, puede crear un objeto
islice
, que es un iterador que
islice
elementos necesarios. Sin embargo, es importante tener en cuenta aquí que esta construcción utiliza todos los elementos del generador hasta el comienzo del corte y todos los elementos en el objeto
islice
.
Omitir inicio de objeto iterable
A veces necesita trabajar con un archivo que, como sabe, comienza con un cierto número de líneas innecesarias, como líneas con comentarios. Para omitir estas líneas, puede recurrir nuevamente a las
itertools
:
string_from_file = """ // Author: ... // License: ... // // Date: ... Actual content... """ import itertools for line in itertools.dropwhile(lambda line: line.startswith("//"), string_from_file.split("\n")): print(line)
Este código solo devuelve líneas después del bloque de comentarios ubicado al comienzo del archivo. Tal enfoque puede ser útil cuando necesita descartar solo elementos (en nuestro caso, líneas) al comienzo del objeto iterable, pero se desconoce su número exacto.
Funciones que admiten solo argumentos con nombre (kwargs)
Para hacerlo posible cuando se utiliza una determinada función de modo que solo se le puedan pasar argumentos con nombre, puede hacer lo siguiente:
def test(*, a, b): pass test("value for a", "value for b")
Esto puede ser útil para mejorar la comprensión del código. Como puede ver, nuestro problema se resuelve fácilmente usando el argumento
*
delante de la lista de argumentos nombrados. Aquí, lo cual es bastante obvio, también puede usar argumentos posicionales, si los coloca antes del argumento
*
.
Crear objetos que admitan la instrucción with
Todo el mundo sabe cómo, por ejemplo, abrir un archivo o, posiblemente, cómo establecer un bloqueo con la instrucción
with
. ¿Pero es posible implementar independientemente el mecanismo de control de bloqueo? Sí, eso es bastante real. El protocolo de gestión del contexto de ejecución se implementa utilizando los métodos
__enter__
y
__exit__
:
class Connection: def __init__(self): ... def __enter__(self):
Esta es la forma más común de implementar las capacidades del administrador de contexto en Python, pero lo mismo se puede hacer más fácilmente:
from contextlib import contextmanager @contextmanager def tag(name): print(f"<{name}>") yield print(f"</{name}>") with tag("h1"): print("This is Title.")
Aquí, el protocolo de gestión de contexto se implementa utilizando el decorador de
contextmanager
. La primera parte de la función de
tag
(antes del
yield
) se ejecuta cuando ingresa el bloque
with
. Este bloque se ejecuta y luego se ejecuta el resto de la función de
tag
.
Ahorre memoria con __slots__
Si alguna vez ha escrito programas que crean un número realmente grande de instancias de una determinada clase, entonces puede notar que dichos programas pueden requerir inesperadamente mucha memoria. Esto se debe a que Python usa diccionarios para representar los atributos de las instancias de clase. Esto tiene un buen efecto en el rendimiento, pero, en términos de consumo de memoria, es ineficiente. Por lo general, sin embargo, esta característica no causa problemas. Sin embargo, si se enfrenta a una falta de memoria en una situación similar, puede intentar usar el atributo
__slots__
:
class Person: __slots__ = ["first_name", "last_name", "phone"] def __init__(self, first_name, last_name, phone): self.first_name = first_name self.last_name = last_name self.phone = phone
Aquí, cuando declaramos el atributo
__slots__
, Python usa una pequeña matriz de un tamaño fijo para almacenar los atributos, no un diccionario. Esto reduce seriamente la cantidad de memoria requerida para cada instancia de la clase. Hay algunos inconvenientes al usar el atributo
__slots__
. Entonces, al usarlo, no podemos declarar nuevos atributos, estamos limitados solo a aquellos que están en
__slots__
. Además, las clases con el atributo
__slots__
no pueden usar herencia múltiple.
CPU y límites de memoria
Si, en lugar de optimizar el programa o mejorar la forma en que usa el procesador, solo necesita establecer una restricción estricta sobre los recursos disponibles, puede usar la biblioteca adecuada:
import signal import resource import os
Esto muestra la limitación del tiempo del procesador y el tamaño de la memoria. Para limitar el uso del procesador por parte del programa, primero obtenemos los valores de límites no duros (blandos) y duros (duros) para un recurso en particular (
RLIMIT_CPU
). Luego establecemos el límite usando un cierto número de segundos especificado por el argumento de
seconds
y el valor límite duro previamente obtenido. Después de eso, registramos el controlador de
signal
, que, cuando se excede el tiempo del procesador asignado al programa, inicia el procedimiento de salida. En el caso de la memoria, nuevamente obtenemos valores para límites rígidos y no rígidos, después de lo cual establecemos el límite utilizando el método de
setrlimit
, al cual pasamos el tamaño de la restricción (
size
) y el valor obtenido previamente del límite rígido.
Controlar lo que se puede importar desde el módulo y lo que no se puede
Algunos lenguajes tienen mecanismos de exportación extremadamente claros desde módulos de variables, métodos e interfaces. Por ejemplo, solo las entidades cuyos nombres comienzan con una letra mayúscula se exportan a Golang. En Python, todo se exporta. Pero solo hasta que se
__all__
atributo
__all__
:
def foo(): pass def bar(): pass __all__ = ["bar"]
En el ejemplo anterior, solo se exportará la función de
bar
. Y si deja vacío el atributo
__all__
, no se exportará nada del módulo. Intentar importar algo de dicho módulo arrojará un error
AttributeError
.
Simplifique la creación de operadores de comparación.
Hay muchos operadores de comparación. Por ejemplo,
__lt__
,
__le__
,
__gt__
,
__ge__
. A pocas personas les gustará la posibilidad de su implementación para una clase determinada. ¿Hay alguna manera de simplificar esta tarea aburrida? Sí, puede
functools.total_ordering
, con la ayuda del decorador
functools.total_ordering
:
from functools import total_ordering @total_ordering class Number: def __init__(self, value): self.value = value def __lt__(self, other): return self.value < other.value def __eq__(self, other): return self.value == other.value print(Number(20) > Number(3)) print(Number(1) < Number(5)) print(Number(15) >= Number(15)) print(Number(10) <= Number(2))
El decorador
functools.total_ordering
usa aquí para simplificar el proceso de implementación del orden de las instancias de clase. Para garantizar su funcionamiento, solo es necesario que se
__lt__
operadores de comparación
__lt__
y
__eq__
. Este es el mínimo que un decorador necesita para construir los operadores de comparación restantes.
Resumen
Esto no quiere decir que todo lo que hablé aquí es absolutamente necesario en el trabajo diario de cada programador de Python. Pero algunas de las técnicas presentadas aquí, de vez en cuando, pueden ser muy útiles. Además, pueden simplificar la solución de problemas, cuya solución habitual puede requerir mucho código y una gran cantidad de trabajo monótono. Además, me gustaría señalar que todo lo que se discutió es parte de la biblioteca estándar de Python. Para ser sincero, algunas de estas características parecen algo inesperadas para la biblioteca estándar. Esto sugiere que si alguien va a implementar en Python algo que no parece del todo común, primero debe hurgar en la biblioteca estándar. Si no puede encontrar inmediatamente lo que necesita allí, entonces tal vez valga la pena, con mucho cuidado, cavar allí. Es cierto que si una búsqueda exhaustiva no tuvo éxito, lo más probable es que lo que necesita realmente no esté allí. Y si es así, debe recurrir a bibliotecas de terceros. En ellos definitivamente se puede encontrar.
Estimados lectores! ¿Conoces alguna característica estándar de Python que pueda parecer bastante inusual a primera vista como "estándar"?
