191 votes

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

Je suis récemment tombé sur une syntaxe je n'ai jamais vu avant, quand j'ai appris le python, ni dans la plupart des tutoriels, de la .. de la notation, il ressemble à quelque chose comme 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, bien sûr):

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

Mais mes questions sont les suivantes:

  • Comment peut-il le faire?
  • Qu'est-ce que ça veux dire avec les deux points?
  • Comment pouvez-vous l'utiliser dans un environnement plus complexe déclaration (si possible)?

Cela va probablement me sauver de nombreuses lignes de code dans le futur...:)

212voto

Paul Rooney Points 12166

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.

74voto

MSeifert Points 6307

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

Permettez-moi de récapituler les questions / réponses: L' .. n'est pas un seul élément syntaxique!

Vous pouvez vérifier le code source est "sous". 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 numéro, le deuxième . est une OP (opérateur, dans ce cas, l'attribut" opérateur) et l' __truediv__ est le nom de la méthode. Donc c'est l'accès à l' __truediv__ méthode du flotteur 1.0.

Une autre façon de visualiser le bytecode généré est-à - disassembler . Cette montre, en réalité, les instructions qui sont exécutées lorsque le 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

Qui dit essentiellement la même. Il charge l'attribut __truediv__ de la constante 1.0.


Concernant votre question

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

Même s'il est possible que vous ne devrait jamais écrire du code comme ça, tout simplement parce qu'il ne sait pas ce que fait le code. Donc merci de ne pas l'utiliser dans plus d'instructions complexes. J'irais même plus loin que vous ne devriez pas l'utiliser dans la "simple", au moins vous devez utiliser des parenthèses pour séparer les instructions:

f = (1.).__truediv__

ce serait vraiment plus lisible - mais quelque chose le long des lignes de:

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

serait encore mieux!

L'approche à l'aide partial préserve python du modèle de données ( 1..__truediv__ approche ne fonctionne pas!) ce qui peut être démontrée 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

C'est parce qu' 1. / (1+2j) n'est pas évalué en float.__truediv__ mais avec complex.__rtruediv__ - operator.truediv fait en sorte que l'opération inverse est appelée lorsque le fonctionnement normal des retours NotImplemented mais vous n'avez pas ces réserves, quand vous travaillez sur __truediv__ directement. Cette perte de "comportement attendu" est la principale raison pour laquelle vous avez (normalement) ne doivent pas utiliser les méthodes magiques directement.

40voto

sobolevn Points 5397

Deux points ensemble peuvent être un peu maladroits 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 littéraux peuvent être écrits sous trois formes:

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

11voto

Aaron Hall Points 7381

Qu'est - f = 1..__truediv__?

f est lié méthode spéciale sur un flotteur avec une valeur de un. Plus précisément,

1.0 / x

en Python 3, invoque:

(1.0).__truediv__(x)

La 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 le faisons:

f = one.__truediv__

Nous conservons un nom lié à celui de la méthode liée

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

Si nous faisions cela parsemé de recherche dans une boucle serrée, cela pourrait sauver un peu de temps.

Analyse de l'Arbre de Syntaxe Abstraite (AST)

Nous pouvons voir que l'analyse de l'AST, l'expression nous dit que nous sommes le __truediv__ d'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 pourriez obtenir la même fonction à partir de:

f = float(1).__truediv__

Ou

f = (1.0).__truediv__

Déduction

On peut aussi y aller par déduction.

Nous allons le construire.

1 par lui-même est une int:

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

1 avec une période 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 un pointillé de recherche sur l'instance du flotteur:

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

Personne ne l'a mentionné - C'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 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 l' divide_one_by fonction est qu'elle nécessite un autre Python cadre de la pile, le rendant légèrement 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 de la plaine des littéraux, 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]

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X