web-dev-qa-db-fra.com

Qu'est-ce que `1 ..__ truediv__`? Est-ce que Python a une syntaxe de notation .. ("point point")?

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

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

print(f(8)) # prints 0.125 

Je pensais que c'était exactement pareil 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 va probablement me sauver beaucoup de lignes de code dans le futur ... :)

190
abccd

Ce que vous avez est un littéral float sans le zéro final, auquel vous accédez ensuite à la méthode __truediv__ de. Ce n'est pas un opérateur en soi; le premier point fait partie de la valeur float et le second est l'opérateur de point permettant d'accéder aux propriétés et méthodes de l'objet.

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.

212
Paul Rooney

La question a déjà fait l'objet d'une réponse suffisante (c'est-à-dire 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: Le .. n'est pas un élément de syntaxe unique!

Vous pouvez vérifier comment le code source est "tokenized" . 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 deuxième . est un OP (un opérateur, dans ce cas l'opérateur "get attribut") et le __truediv__ est le nom de la méthode. Il ne s'agit donc que d'accéder à la méthode __truediv__ du float 1.0.

Une autre façon d’afficher le pseudo-code généré consiste à disassemble le. Cela montre en fait les instructions qui sont exécutées quand du code est exécuté:

>>> 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 ta 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 un code comme celui-ci, simplement parce que son code n'est pas clair. Alors s'il vous plaît ne l'utilisez pas dans des déclarations plus complexes. J'irais même jusqu'à dire que vous ne devriez pas l'utiliser dans des énoncés aussi "simples". Au moins, vous devriez utiliser une parenthèse pour séparer les instructions:

f = (1.).__truediv__

cela serait nettement plus lisible - mais quelque chose dans le sens de:

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

serait encore mieux!

L'approche utilisant partial préserve également le modèle de données de python (l'approche 1..__truediv__ ne le fait pas!), Ce qui peut être démontré par ce petit extrait:

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

En effet, 1. / (1+2j) n'est pas évalué par float.__truediv__ mais par complex.__rtruediv__ - operator.truediv s'assure que l'opération inverse est appelée lorsque l'opération normale renvoie NotImplemented mais vous ne le faites pas. Vous n’avez pas ces retombées lorsque vous utilisez directement __truediv__. Cette perte de "comportement attendu" est la principale raison pour laquelle vous (normalement) ne devriez pas utiliser directement les méthodes magiques.

73
MSeifert

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

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

Mais c'est comme écrire:

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

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

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

Qu'est-ce que f = 1..__truediv__?

f est une méthode spéciale liée sur un float avec la valeur un. Plus précisément,

1.0 / x

dans Python 3, appelle:

(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

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

Si nous effectuions cette recherche en boucle dans une boucle étroite, cela pourrait faire gagner un peu de temps.

Analyse de l'arbre de syntaxe abstraite (AST)

Nous pouvons voir que l'analyse de AST pour l'expression nous indique que nous obtenons l'attribut __truediv__ 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 de:

f = float(1).__truediv__

Ou

f = (1.0).__truediv__

Déduction

On peut aussi y arriver par déduction.

Construisons-le.

1 en lui-même est un int:

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

1 avec un point après c'est un float:

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

Le prochain point par lui-même serait une SyntaxError, mais il commence une recherche en pointillé sur l'instance du float:

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

Personne d'autre n'en a parlé - Ceci est maintenant une "méthode liée" sur le float, 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 beaucoup plus facilement:

>>> 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 fonction divide_one_by 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 ne pouvez utiliser que 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]
11
Aaron Hall