Qu'est-ce que «1 ..__ truediv__»? Python a-t-il une syntaxe de notation .. («dot dot»)?

190

Je suis récemment tombé sur une syntaxe que je n'avais jamais vue auparavant lorsque j'ai appris python ni dans la plupart des tutoriels, la ..notation, ça ressemble à ceci:

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

print(f(8)) # prints 0.125 

J'ai pensé que c'était exactement la même chose que (sauf que c'est plus long, bien sûr):

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

Mais mes questions sont:

  • Comment peut-il faire ça?
  • Qu'est-ce que cela signifie réellement avec les deux points?
  • Comment pouvez-vous l'utiliser dans une déclaration plus complexe (si possible)?

Cela me fera probablement économiser de nombreuses lignes de code à l'avenir ... :)

abccd
la source
14
Remarque: ce (1).__truediv__n'est pas vraiment le même que 1..__truediv__, comme le premier appelle int.__truediv__tandis que le second le fait float.__truediv__. Alternativement, vous pouvez également utiliser 1 .__truediv__(avec un espace) `
tobias_k
7
Notez que ce 1//8n'est 0pas le cas 0.125dans les deux versions de Python.
mkrieger1
1
me rappelleif (x <- 3) {...}
Dunno
7
Voici un exemple d'utilisation.
Éamonn Olive
3
@KeithC Les réponses et les commentaires de haute qualité montrent que l'exemple de code a besoin d'un aperçu pour être compris, est surprenant pour beaucoup, a des alternatives plus claires, plus générales et au moins aussi efficaces. Mon principal reproche est que la lisibilité compte. Gardez votre intelligence là où vous en avez le plus besoin: communiquer avec les humains.
Peter Wood

Réponses:

212

Ce que vous avez est un floatlittéral sans le zéro final, dont vous accédez ensuite à la __truediv__méthode. Ce n'est pas un opérateur en soi; le premier point fait partie de la valeur flottante et le second est l'opérateur point pour accéder aux propriétés et méthodes des objets.

Vous pouvez atteindre le même point en procédant comme suit.

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

Un autre exemple

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

Ici, nous ajoutons 1.0 à 2.0, ce qui donne évidemment 3.0.

Paul Rooney
la source
165
Nous avons donc trouvé un développeur qui a sacrifié beaucoup de clarté pour un peu de brièveté et nous y voilà.
TemporalWolf
11
Peut-être que quelqu'un enregistre son code source sur une disquette de 5,5 "?
Thomas Ayoub
10
@ThomasAyoub ce serait 5,25 "iirc ;-)
jjmontes
9
@TemporalWolf Il l'a peut-être trouvé dans cette récente soumission de code de golf .
Brian McCutchon
2
Fait amusant, vous pouvez également le faire en JavaScript:1..toString()
Derek 朕 會 功夫
74

La question est déjà suffisamment répondue (c'est-à-dire la réponse de @Paul Rooney ) mais il est également possible de vérifier l'exactitude de ces réponses.

Permettez-moi de récapituler les réponses existantes: ce ..n'est pas un élément de syntaxe unique!

Vous pouvez vérifier comment le code source est "tokenisé" . Ces jetons représentent la façon dont le code est interprété:

>>> 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__'),
 ...]

Ainsi, la chaîne 1.est interprétée comme un nombre, le second .est un OP (un opérateur, dans ce cas l'opérateur "get attribute") et le __truediv__est le nom de la méthode. Il s'agit donc simplement d'accéder à la __truediv__méthode du flotteur 1.0.

Une autre façon de visualiser le bytecode généré est de l' assembler . Cela montre en fait les instructions qui sont exécutées lorsque du code est exécuté: dis

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

Ce qui dit fondamentalement la même chose. Il charge l'attribut __truediv__de la constante 1.0.


Concernant votre question

Et comment pouvez-vous l'utiliser dans une déclaration plus complexe (si possible)?

Même si c'est possible, vous ne devriez jamais écrire de code comme ça, simplement parce que ce que fait le code n'est pas clair. Ne l'utilisez donc pas dans des déclarations plus complexes. J'irais même si loin que vous ne devriez pas l'utiliser dans des déclarations si "simples", au moins vous devriez utiliser des parenthèses pour séparer les instructions:

f = (1.).__truediv__

ce serait certainement plus lisible - mais quelque chose du genre:

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

serait encore mieux!

L'approche utilisant partialpréserve également le modèle de données de python (l' 1..__truediv__approche ne le fait pas!) Qui peut être démontré par ce petit extrait de code:

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

C'est parce que 1. / (1+2j)n'est pas évalué par float.__truediv__mais avec complex.__rtruediv__- operator.truedivs'assure que l'opération inverse est appelée lorsque l'opération normale revient, NotImplementedmais que vous n'avez pas ces solutions de secours lorsque vous opérez __truediv__directement. Cette perte de «comportement attendu» est la principale raison pour laquelle vous ne devriez pas (normalement) utiliser directement les méthodes magiques.

MSeifert
la source
40

Deux points ensemble peuvent être un peu gênants au début:

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

Mais c'est la même chose que d'écrire:

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

Parce que les floatlittéraux peuvent être écrits sous trois formes:

normal_float = 1.0
short_float = 1.  # == 1.0
prefixed_float = .1  # == 0.1
sobolevn
la source
Cela est surprenant, pourquoi cette syntaxe 1.__truediv__est-elle valide mais ne l'est pas?
Alex Hall
3
@AlexHall Voir ici . Le .semble être analysé comme faisant partie du nombre, puis l' .accesseur de méthode est manquant.
tobias_k
7
Mais comme c'est une syntaxe maladroite et peu claire, elle devrait probablement être évitée.
DrMcCleod
11

C'est quoi f = 1..__truediv__?

fest une méthode spéciale liée sur un flottant avec une valeur de un. Plus précisément,

1.0 / x

en Python 3, invoque:

(1.0).__truediv__(x)

Preuve:

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

et:

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

Si nous faisons:

f = one.__truediv__

Nous conservons un nom lié à cette méthode liée

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

Si nous faisions cette recherche en pointillé dans une boucle serrée, cela pourrait gagner un peu de temps.

Analyse de l'arbre de syntaxe abstraite (AST)

On peut voir que l' analyse de l'AST pour l'expression nous dit que nous obtenons l' __truediv__attribut sur le nombre à virgule flottante, 1.0:

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

Vous pouvez obtenir la même fonction résultante à partir de:

f = float(1).__truediv__

Ou

f = (1.0).__truediv__

Déduction

On peut aussi y arriver par déduction.

Construisons-le.

1 en soi est un int:

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

1 avec un point après qu'il soit un flotteur:

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

Le point suivant en lui-même serait une SyntaxError, mais il commence une recherche pointée sur l'instance du flottant:

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

Personne n'a mentionné ailleurs ce - Ceci est maintenant une « méthode liée » sur le flotteur, 1.0:

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

Nous pourrions accomplir la même fonction de manière beaucoup plus lisible:

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

Performance

L'inconvénient de la divide_one_byfonction est qu'elle nécessite un autre cadre de pile Python, ce qui la rend un peu plus lente que la méthode liée:

>>> 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]

Bien sûr, si vous pouvez simplement utiliser des littéraux simples, c'est encore plus rapide:

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