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 ... :)
(1).__truediv__
n'est pas vraiment le même que1..__truediv__
, comme le premier appelleint.__truediv__
tandis que le second le faitfloat.__truediv__
. Alternativement, vous pouvez également utiliser1 .__truediv__
(avec un espace) `1//8
n'est0
pas le cas0.125
dans les deux versions de Python.if (x <- 3) {...}
Réponses:
Ce que vous avez est un
float
litté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.
la source
1..toString()
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 flotteur1.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 constante1.0
.Concernant votre question
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
partial
pré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é parfloat.__truediv__
mais aveccomplex.__rtruediv__
-operator.truediv
s'assure que l'opération inverse est appelée lorsque l'opération normale revient,NotImplemented
mais 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.la source
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
float
littéraux peuvent être écrits sous trois formes:normal_float = 1.0 short_float = 1. # == 1.0 prefixed_float = .1 # == 0.1
la source
1.__truediv__
est-elle valide mais ne l'est pas?.
semble être analysé comme faisant partie du nombre, puis l'.
accesseur de méthode est manquant.f
est 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:
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_by
fonction 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]
la source