¿Qué es python. ("punto punto") sintaxis de notación?


Recientemente me encontré con una sintaxis que nunca había visto antes 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 se puede utilizar en una declaración más compleja (si es posible)?

Esto probablemente me ahorra muchas líneas de código en el futuro...:)

Author: Paul Rooney, 2017-04-19

4 answers

Lo que tienes es un float literal sin el cero final, del que luego accedes al método __truediv__. 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í agregamos 1.0 a 2.0, lo que obviamente produce 3.0.

 212
Author: Paul Rooney,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-04-19 22:56:58

La pregunta ya está suficientemente contestada (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!

Puede comprobar cómo el código fuente es "acorta". 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__'),
 ...]

Así que la cadena 1. se interpreta como número, el segundo . es un OP (un operador, en este caso el operador "get attribute") y el __truediv__ es el nombre del método. Así que esto es solo acceder al método __truediv__ del flotador 1.0.

Otra forma de ver el bytecode generado es dismontar es. Esto en realidad 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 se puede utilizar en una declaración más compleja (si es posible)?

Aunque es posible que nunca debas escribir código así, simplemente porque no está claro qué está haciendo el código. Así que, por favor, no lo use en declaraciones más complejas. Incluso iría tan lejos que no debería usarlo en declaraciones tan "simples", al menos debería 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 que usa {[14] } también conserva el modelo de datos de python (¡el enfoque 1..__truediv__ no lo hace!) que 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 debido a que 1. / (1+2j) no es evaluado por float.__truediv__, pero con complex.__rtruediv__ - operator.truediv se asegura de que la operación inversa se llama cuando la operación normal devuelve NotImplemented pero usted no tiene estas funciones, cuando opera sobre __truediv__ directamente. Esta pérdida del "comportamiento esperado" es la razón principal por la que (normalmente) no debería usar métodos mágicos directamente.

 73
Author: MSeifert,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-04-21 10:35:19

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
 40
Author: sobolevn,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-04-19 07:55:09

¿Qué es f = 1..__truediv__?

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

1.0 / x

En Python 3, invoca: {[28]]}

(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 lo hacemos:{[28]]}

f = one.__truediv__

Retenemos 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 bucle apretado, esto podría ahorrar un poco de tiempo.

Analizando el Árbol de Sintaxis Abstracta (AST)

Podemos ver que el análisis de la AST para la expresión nos dice que estamos obteniendo el atributo __truediv__ en el número de coma 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()))"

Puede obtener la misma función resultante de:

f = float(1).__truediv__

O

f = (1.0).__truediv__

Deducción

También podemos llegar 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 flotador:

>>> 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: {[28]]}

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

Rendimiento

La desventaja de la función divide_one_by es que requiere otro marco de pila de Python, lo que la hace algo más lenta que el método bound:

>>> 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]
 11
Author: Aaron Hall,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-01-04 00:41:46