Consejos útiles de Python que no has conocido

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) # This string has some whitespaces... " 

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) # <itertools.islice object at 0x7f70fab88138> for val in s: ... 

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") # TypeError: test() takes 0 positional arguments... test(a="value", b="value 2") #   - ... 

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):  #  ... def __exit__(self, type, value, traceback):  #  ... with Connection() as c: # __enter__() executes ... # conn.__exit__() executes 

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 #     def time_exceeded(signo, frame): print("CPU exceeded...") raise SystemExit(1) def set_max_runtime(seconds): #   signal     soft, hard = resource.getrlimit(resource.RLIMIT_CPU) resource.setrlimit(resource.RLIMIT_CPU, (seconds, hard)) signal.signal(signal.SIGXCPU, time_exceeded) #     def set_max_memory(size): soft, hard = resource.getrlimit(resource.RLIMIT_AS) resource.setrlimit(resource.RLIMIT_AS, (size, hard)) 

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


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


All Articles