El libro "Pure Python. Las sutilezas de la programación para profesionales »

imagen Hola habrozhiteli! Aprender todas las posibilidades de Python es una tarea difícil, y con este libro puedes enfocarte en habilidades prácticas que son realmente importantes. Excava oro oculto en la biblioteca estándar de Python y comienza a escribir código limpio hoy.

Si tiene experiencia con versiones anteriores de Python, puede acelerar el trabajo con plantillas y funciones modernas introducidas en Python 3.

Si ha trabajado con otros lenguajes de programación y desea cambiar a Python, encontrará consejos prácticos necesarios para convertirse en un pitonista efectivo.
Si desea aprender a escribir código limpio, encontrará aquí los ejemplos más interesantes y los trucos poco conocidos.

Extracto "La expresión de diccionario más loca en Occidente"


A veces te encuentras con un pequeño ejemplo de código que tiene una profundidad verdaderamente inesperada: una sola línea de código que puede enseñarte mucho si lo piensas cuidadosamente. Tal código es como un koan en el budismo zen: una pregunta o declaración utilizada en la práctica zen para plantear dudas y evaluar el rendimiento de los estudiantes.

El pequeño fragmento de código que discutimos en esta sección es uno de esos ejemplos. A primera vista, puede parecer una expresión de vocabulario simple, pero después de un examen más detallado, lo envía a un crucero psicodélico que expande la mente con el intérprete de Python.

Recibo tanta atención de esta frase que incluso una vez la imprimí en mi credencial de participante de la conferencia de Python como motivo de una conversación. Esto llevó a algunos diálogos constructivos con miembros de mi lista de correo electrónico de Python.
Entonces, sin más preámbulos, aquí está este fragmento de código. Tómese un descanso para reflexionar sobre la expresión de vocabulario a continuación y hacia qué debe conducir su cálculo:

>>> {True: '', 1: '', 1.0: ''} 

Esperaré aquí ...

Ok listo?

El siguiente es el resultado que obtenemos al evaluar la expresión del diccionario anterior en una sesión de intérprete de Python:

 >>> {True: '', 1: '', 1.0: ''} {True: ''} 

Admito que cuando vi este resultado por primera vez, me quedé muy atónito. Pero todo encajará cuando realice un estudio pausado, paso a paso, de lo que está sucediendo aquí. Consideremos por qué obtenemos esto, debo decir que no es un resultado muy intuitivo.

Cuando Python procesa nuestra expresión de diccionario, primero crea un nuevo objeto de diccionario vacío, y luego le asigna claves y valores en el orden en que se pasan a la expresión de diccionario.

Luego, cuando lo descomponemos en partes, nuestra expresión de diccionario será equivalente a la siguiente secuencia de instrucciones que se ejecutan en orden:

 >>> xs = dict() >>> xs[True] = '' >>> xs[1] = '' >>> xs[1.0] = '' 

Curiosamente, Python considera que todas las claves utilizadas en este ejemplo de diccionario son equivalentes:

 >>> True == 1 == 1.0 True 

Está bien, pero espera un minuto. Estoy seguro de que puede admitir intuitivamente que 1.0 == 1, pero ¿por qué True también se considera equivalente a 1? La primera vez que vi esta expresión del diccionario, realmente me dejó perplejo.

Hurgando un poco en la documentación de Python, descubrí que Python trata el tipo bool como una subclase del tipo int. Este es el caso en Python 2 y Python 3:

El tipo booleano es un subtipo del tipo entero, y los valores booleanos se comportan, respectivamente, como los valores 0 y 1 en casi todos los contextos, con la excepción de que cuando se convierten en un tipo de cadena, los valores de la cadena se devuelven 'Falso' o 'Verdadero, respectivamente '.

Y, por supuesto, esto significa que en Python, los valores booleanos se pueden usar técnicamente como índices de lista o tupla:

 >>> ['', ''][True] '' 

Pero probablemente no debería usar este tipo de variable lógica en nombre de la claridad (y la salud mental de sus colegas).

De una forma u otra, volvamos a la expresión de nuestro diccionario.

En cuanto al lenguaje Python, todos estos valores, True, 1 y 1.0, representan la misma clave del diccionario. Cuando el intérprete evalúa una expresión de diccionario, sobrescribe repetidamente el valor de la clave True. Esto explica por qué al final el diccionario resultante contiene solo una clave.

Antes de continuar, observe nuevamente la expresión original del diccionario:

 >>> {True: '', 1: '', 1.0: ''} {True: ''} 

¿Por qué seguimos obteniendo True como la clave aquí? ¿No debería la clave también cambiar a 1.0 debido a tareas repetidas al final?

Después de un poco de investigación en el código fuente del intérprete de Python, descubrí que cuando se asocia un nuevo valor con un objeto clave, los diccionarios de Python no actualizan este objeto clave:

 >>> ys = {1.0: ''} >>> ys[True] = '' >>> ys {1.0: ''} 

Por supuesto, esto tiene sentido como una optimización del rendimiento: si las claves se consideran idénticas, ¿por qué perder el tiempo actualizando el original?
En el último ejemplo, viste que el objeto True original como clave nunca se reemplaza. Por esta razón, la representación de cadena del diccionario todavía imprime la clave como Verdadero (en lugar de 1 o 1.0).

Con lo que ahora sabemos, aparentemente, los valores en el diccionario resultante se reescriben solo porque la comparación siempre los mostrará como equivalentes entre sí. Sin embargo, resulta que este efecto tampoco es una consecuencia de la prueba de equivalencia mediante el método __eq__.

Los diccionarios de Python se basan en una estructura de datos de tabla hash. Cuando vi por primera vez esta increíble expresión de diccionario, mi primer pensamiento fue que tal comportamiento estaba de alguna manera relacionado con conflictos hash.

El hecho es que la tabla hash en la representación interna almacena las claves disponibles en varias "cestas" de acuerdo con el valor hash de cada clave. El valor hash se deriva de la clave como un valor numérico de longitud fija que identifica de forma exclusiva la clave.

Este hecho le permite realizar operaciones de búsqueda rápida. Encontrar el valor de clave hash en la tabla de búsqueda es mucho más rápido que comparar el objeto clave completo con todas las demás claves y realizar una verificación de equivalencia.

Sin embargo, los métodos para calcular valores hash generalmente no son ideales. Y en última instancia, dos o más claves que son realmente diferentes tendrán el mismo valor hash derivado, y terminarán en la misma cesta de la tabla de búsqueda.
Cuando dos claves tienen el mismo valor hash, esta situación se denomina conflicto hash y es un caso especial con el que se deben tratar los algoritmos para insertar y encontrar elementos en una tabla hash.

Según esta evaluación, es muy probable que el hash esté relacionado de alguna manera con el resultado inesperado que obtuvimos de la expresión de nuestro diccionario. Por lo tanto, descubramos si los valores clave de hash también juegan un cierto papel aquí.
Defino la clase a continuación como una pequeña herramienta de detective:

 class AlwaysEquals: def __eq__(self, other): return True def __hash__(self): return id(self) 

Esta clase se caracteriza por dos aspectos.

En primer lugar, dado que el método __eq__ Dunder siempre devuelve True, todas las instancias de esta clase pretenden ser equivalentes a cualquier objeto:

 >>> AlwaysEquals() == AlwaysEquals() True >>> AlwaysEquals() == 42 True >>> AlwaysEquals() == '?' True 

Y en segundo lugar, cada instancia de AlwaysEquals también devolverá un valor hash único generado por la función incorporada id ():

 >>> objects = [AlwaysEquals(), AlwaysEquals(), AlwaysEquals()] >>> [hash(obj) for obj in objects] [4574298968, 4574287912, 4574287072] 

En Python, la función id () devuelve la dirección de un objeto en la RAM, que se garantiza que es única.

Con esta clase, ahora puede crear objetos que pretenden que son equivalentes a cualquier otro objeto, pero al mismo tiempo tienen un valor hash único asociado con ellos. Esto le permitirá verificar si las claves del diccionario se reescriben, basándose únicamente en el resultado de su comparación de equivalencia.

Y, como puede ver, las teclas en el siguiente ejemplo no corresponden, aunque la comparación siempre las mostrará como equivalentes entre sí:

 >>> {AlwaysEquals(): '', AlwaysEquals(): ''} { <AlwaysEquals object at 0x110a3c588>: '', <AlwaysEquals object at 0x110a3cf98>: '' } 

También podemos ver esta idea desde el otro lado y verificar si devolver el mismo valor hash es razón suficiente para forzar la reescritura de las claves:

 class SameHash: def __hash__(self): return 1 

La comparación de instancias de la clase SameHash los mostrará como no equivalentes entre sí, pero todos tendrán el mismo valor hash de 1:

 >>> a = SameHash() >>> b = SameHash() >>> a == b False >>> hash(a), hash(b) (1, 1) 

Veamos cómo responden los diccionarios de Python cuando intentamos usar instancias de clase SameHash como claves de diccionario:

 >>> {a: 'a', b: 'b'} { <SameHash instance at 0x7f7159020cb0>: 'a', <SameHash instance at 0x7f7159020cf8>: 'b' } 

Como muestra este ejemplo, el efecto de "sobrescribir las claves" se debe no solo a conflictos de valores hash.

Los diccionarios realizan una verificación de equivalencia y comparan el valor hash para determinar si las dos claves son las mismas. Intentemos resumir los resultados de nuestro estudio.

La expresión del diccionario {True: 'yes', 1: 'no', 1.0: 'maybe'} se calcula como {True: 'possible'}, porque al comparar todas las claves de este ejemplo, True, 1 y 1.0, se mostrarán como equivalentes entre sí, y todos tienen el mismo valor hash:

 >>> True == 1 == 1.0 True >>> (hash(True), hash(1), hash(1.0)) (1, 1, 1) 

Quizás ahora no sea tan sorprendente que obtuvimos un resultado tal como el estado final del diccionario:

 >>> {True: '', 1: '', 1.0: ''} {True: ''} 

Aquí cubrimos muchos temas, y este truco de Python en particular puede no encajar en la cabeza al principio; es por eso que al comienzo de la sección lo comparé con un koan zen.

Si tiene dificultades para comprender lo que está sucediendo en esta sección, intente experimentar con todos los ejemplos de código en una sesión de intérprete de Python. Será recompensado al ampliar su conocimiento de los mecanismos internos del lenguaje Python.

»Se puede encontrar más información sobre el libro en el sitio web del editor
» Contenidos
» Extracto

Cupón de 20% de descuento para vendedores ambulantes - Python

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


All Articles