¿Qué es `1..__truediv__`? ¿Python tiene una sintaxis de notación .. (“punto punto”)?

8 minutos de lectura

avatar de usuario
Taku

Recientemente me encontré con una sintaxis que nunca antes había visto cuando aprendí python ni en la mayoría de los tutoriales, la .. notación, se ve algo como esto:

f = 1..__truediv__ # or 1..__div__ for python 2

print(f(8)) # prints 0.125 

Pensé que era exactamente lo mismo que (excepto que es más largo, por supuesto):

f = lambda x: (1).__truediv__(x)
print(f(8)) # prints 0.125 or 1//8

Pero mis preguntas son:

  • ¿Cómo puede hacer eso?
  • ¿Qué significa realmente con los dos puntos?
  • ¿Cómo puede usarlo en una declaración más compleja (si es posible)?

Esto probablemente me ahorrará muchas líneas de código en el futuro… 🙂

  • Nota: (1).__truediv__ no es realmente lo mismo que 1..__truediv__como llama el primero int.__truediv__ mientras que este último lo hace float.__truediv__. Alternativamente, también puede usar 1 .__truediv__ (con un espacio)`

    – tobias_k

    19 de abril de 2017 a las 10:49

  • Tenga en cuenta que 1//8 es 0no 0.125en cualquier versión de Python.

    – mkrieger1

    19/04/2017 a las 11:40


  • me recuerda a if (x <- 3) {...}

    – No sé

    19 de abril de 2017 a las 13:19

  • Aquí es un ejemplo de esto en uso.

    – Mago de trigo

    19 de abril de 2017 a las 13:57

  • @KeithC Las respuestas y comentarios de alta calidad muestran que el código de muestra necesita información para comprenderlo, es sorprendente para muchos, tiene alternativas que son más claras, más generales y al menos tan eficientes. Mi queja principal es que la legibilidad cuenta. Guarde la inteligencia para donde más se necesita: comunicarse con los humanos.

    – Pedro Madera

    2 mayo 2017 a las 7:00


avatar de usuario
Pablo Rooney

lo que tienes es un float literal sin el cero final, al que luego accede al __truediv__ método de. No es un operador en sí mismo; el primer punto es parte del valor flotante y el segundo es el operador de punto para acceder a las propiedades y métodos de los objetos.

Puede llegar al mismo punto haciendo lo siguiente.

>>> f = 1.
>>> f
1.0
>>> f.__floordiv__
<method-wrapper '__floordiv__' of float object at 0x7f9fb4dc1a20>

Otro ejemplo

>>> 1..__add__(2.)
3.0

Aquí sumamos 1,0 a 2,0, lo que obviamente da como resultado 3,0.

  • Entonces, lo que encontramos es un desarrollador que sacrificó mucha claridad por un poco de brevedad y aquí estamos.

    – TemporalWolf

    19 de abril de 2017 a las 7:31

  • ¿Quizás alguien está guardando su código fuente en un disquete de 5,5″?

    – Thomas Ayoub

    19/04/2017 a las 15:31

  • @ThomasAyoub sería 5.25″ iirc 😉

    – jjmontes

    19/04/2017 a las 15:50

  • @TemporalWolf Es posible que lo haya encontrado en esta reciente presentación de código de golf.

    – Brian McCutchon

    19 de abril de 2017 a las 20:59


  • Dato curioso, también puedes hacer esto en JavaScript: 1..toString()

    – Derek 朕會功夫

    21 de abril de 2017 a las 3:56


avatar de usuario
MSeifert

La pregunta ya está suficientemente respondida (es decir, la respuesta de @Paul Rooney), pero también es posible verificar la exactitud de estas respuestas.

Permítanme recapitular las respuestas existentes: El .. no es un solo elemento de sintaxis!

Puedes comprobar cómo es el código fuente “tokenizado”. Estos tokens representan cómo se interpreta el código:

>>> from tokenize import tokenize
>>> from io import BytesIO

>>> s = "1..__truediv__"
>>> list(tokenize(BytesIO(s.encode('utf-8')).readline))
[...
 TokenInfo(type=2 (NUMBER), string='1.', start=(1, 0), end=(1, 2), line="1..__truediv__"),
 TokenInfo(type=53 (OP), string='.', start=(1, 2), end=(1, 3), line="1..__truediv__"),
 TokenInfo(type=1 (NAME), string='__truediv__', start=(1, 3), end=(1, 14), line="1..__truediv__"),
 ...]

Entonces la cuerda 1. se interpreta como número, el segundo . es un OP (un operador, en este caso el operador “obtener atributo”) y el __truediv__ es el nombre del método. Así que esto es sólo acceder a la __truediv__ método del flotador 1.0.

Otra forma de ver el bytecode generado es disarmar eso. Esto realmente muestra las instrucciones que se realizan cuando se ejecuta algún código:

>>> import dis

>>> def f():
...     return 1..__truediv__

>>> dis.dis(f)
  4           0 LOAD_CONST               1 (1.0)
              3 LOAD_ATTR                0 (__truediv__)
              6 RETURN_VALUE

Que básicamente dice lo mismo. Carga el atributo __truediv__ de la constante 1.0.


Con respecto a su pregunta

¿Y cómo puede usarlo en una declaración más compleja (si es posible)?

Aunque es posible que nunca debas escribir un código como ese, simplemente porque no está claro qué está haciendo el código. Por lo tanto, no lo use en declaraciones más complejas. Incluso iría tan lejos que no deberías usarlo en declaraciones tan “simples”, al menos deberías usar paréntesis para separar las instrucciones:

f = (1.).__truediv__

esto sería definitivamente más legible, pero algo así como:

from functools import partial
from operator import truediv
f = partial(truediv, 1.0)

sería aún mejor!

El enfoque utilizando partial también conserva modelo de datos de python (la 1..__truediv__ ¡el enfoque no lo hace!) Lo cual se puede demostrar con este pequeño fragmento:

>>> f1 = 1..__truediv__
>>> f2 = partial(truediv, 1.)

>>> f2(1+2j)  # reciprocal of complex number - works
(0.2-0.4j)
>>> f2('a')   # reciprocal of string should raise an exception
TypeError: unsupported operand type(s) for /: 'float' and 'str'

>>> f1(1+2j)  # reciprocal of complex number - works but gives an unexpected result
NotImplemented
>>> f1('a')   # reciprocal of string should raise an exception but it doesn't
NotImplemented

Esto es porque 1. / (1+2j) no es evaluada por float.__truediv__ pero con complex.__rtruediv__operator.truediv se asegura de que se llame a la operación inversa cuando regrese la operación normal NotImplemented pero no tiene estos respaldos cuando opera en __truediv__ directamente. Esta pérdida del “comportamiento esperado” es la razón principal por la que (normalmente) no deberías usar métodos mágicos directamente.

avatar de usuario
sobolevn

Dos puntos juntos pueden ser un poco incómodos al principio:

f = 1..__truediv__ # or 1..__div__ for python 2

Pero es lo mismo que escribir:

f = 1.0.__truediv__ # or 1.0.__div__ for python 2

Porque float Los literales se pueden escribir de tres formas:

normal_float = 1.0
short_float = 1.  # == 1.0
prefixed_float = .1  # == 0.1

  • Esto es sorprendente, ¿por qué esta sintaxis es válida pero 1.__truediv__ ¿no es?

    – Alex Salón

    19 de abril de 2017 a las 11:54

  • @AlexHall Ver aquí. los . parece ser analizado como parte del número, y luego el . porque falta el descriptor de acceso del método.

    – tobias_k

    19 de abril de 2017 a las 12:01

  • Pero dado que es una sintaxis incómoda y poco clara, probablemente debería evitarse.

    – Dr. McCleod

    19 de abril de 2017 a las 14:09

avatar de usuario
Rusia debe sacar a Putin

Que es f = 1..__truediv__?

f es un método especial enlazado en un flotante con un valor de uno. Específicamente,

1.0 / x

en Python 3, invoca:

(1.0).__truediv__(x)

Evidencia:

class Float(float):
    def __truediv__(self, other):
        print('__truediv__ called')
        return super(Float, self).__truediv__(other)

y:

>>> one = Float(1)
>>> one/2
__truediv__ called
0.5

Si hacemos:

f = one.__truediv__

Conservamos un nombre vinculado a ese método vinculado

>>> f(2)
__truediv__ called
0.5
>>> f(3)
__truediv__ called
0.3333333333333333

Si estuviéramos haciendo esa búsqueda punteada en un ciclo cerrado, esto podría ahorrar un poco de tiempo.

Análisis del árbol de sintaxis abstracta (AST)

Podemos ver que analizar el AST para la expresión nos dice que estamos obteniendo el __truediv__ atributo en el número de punto flotante, 1.0:

>>> import ast
>>> ast.dump(ast.parse('1..__truediv__').body[0])
"Expr(value=Attribute(value=Num(n=1.0), attr="__truediv__", ctx=Load()))"

Podría obtener la misma función resultante de:

f = float(1).__truediv__

O

f = (1.0).__truediv__

Deducción

También podemos llegar allí por deducción.

Vamos a construirlo.

1 por sí mismo es un int:

>>> 1
1
>>> type(1)
<type 'int'>

1 con un punto después de que es un flotante:

>>> 1.
1.0
>>> type(1.)
<type 'float'>

El siguiente punto por sí mismo sería un SyntaxError, pero comienza una búsqueda punteada en la instancia del flotador:

>>> 1..__truediv__
<method-wrapper '__truediv__' of float object at 0x0D1C7BF0>

Nadie más ha mencionado esto – Esto es ahora un “método enlazado” en el flotador, 1.0:

>>> f = 1..__truediv__
>>> f
<method-wrapper '__truediv__' of float object at 0x127F3CD8>
>>> f(2)
0.5
>>> f(3)
0.33333333333333331

Podríamos lograr la misma función mucho más legible:

>>> def divide_one_by(x):
...     return 1.0/x
...     
>>> divide_one_by(2)
0.5
>>> divide_one_by(3)
0.33333333333333331

Actuación

La desventaja de la divide_one_by La función es que requiere otro marco de pila de Python, lo que lo hace un poco más lento que el método enlazado:

>>> def f_1():
...     for x in range(1, 11):
...         f(x)
...         
>>> def f_2():
...     for x in range(1, 11):
...         divide_one_by(x)
...         
>>> timeit.repeat(f_1)
[2.5495760687176485, 2.5585621018805469, 2.5411816588331888]
>>> timeit.repeat(f_2)
[3.479687248616699, 3.46196088706062, 3.473726342237768]

Por supuesto, si solo puede usar literales simples, eso es aún más rápido:

>>> def f_3():
...     for x in range(1, 11):
...         1.0/x
...         
>>> timeit.repeat(f_3)
[2.1224895628296281, 2.1219930218637728, 2.1280188256941983]

¿Ha sido útil esta solución?