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:
Cela va probablement me sauver beaucoup de lignes de code dans le futur ... :)
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.
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 à dis
assemble 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.
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
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.
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__
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
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]