Was ist `1 ..__ truediv__`? Hat Python eine .. ("Punkt Punkt") Notationssyntax?


190

Ich bin kürzlich auf eine Syntax gestoßen, die ich noch nie gesehen habe, als ich Python gelernt habe, und in den meisten Tutorials ..sieht die Notation ungefähr so ​​aus:

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

print(f(8)) # prints 0.125 

Ich dachte, es wäre genau das gleiche wie (außer es ist natürlich länger):

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

Aber meine Fragen sind:

  • Wie kann es das machen?
  • Was bedeutet das eigentlich mit den beiden Punkten?
  • Wie können Sie es in einer komplexeren Anweisung verwenden (wenn möglich)?

Dies wird mir wahrscheinlich in Zukunft viele Codezeilen ersparen ... :)


14
Hinweis: (1).__truediv__ist nicht wirklich dasselbe wie 1..__truediv__, wie der erstere anruft, int.__truediv__während der letztere es tut float.__truediv__. Alternativ können Sie auch 1 .__truediv__(mit einem Leerzeichen) `
tobias_k

7
Beachten Sie, dass 1//8ist 0nicht 0.125, in beiden Versionen von Python.
mkrieger1

1
erinnert mich anif (x <- 3) {...}
Keine Ahnung

7
Hier ist ein Beispiel dafür.
Éamonn Olive

3
@KeithC Die qualitativ hochwertigen Antworten und Kommentare zeigen, dass der Beispielcode Einblicke benötigt, um zu verstehen, für viele überraschend ist, Alternativen bietet, die klarer, allgemeiner und mindestens genauso effizient sind. Mein Hauptkritikpunkt ist, dass die Lesbarkeit zählt. Speichern Sie Cleverness dort, wo es am dringendsten benötigt wird - in der Kommunikation mit Menschen.
Peter Wood

Antworten:


212

Was Sie haben, ist ein floatLiteral ohne die nachfolgende Null, auf das Sie dann zugreifen __truediv__. Es ist kein Operator an sich; Der erste Punkt ist Teil des Gleitkommawerts und der zweite Punkt ist der Punktoperator für den Zugriff auf die Objekteigenschaften und -methoden.

Sie können den gleichen Punkt erreichen, indem Sie die folgenden Schritte ausführen.

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

Ein anderes Beispiel

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

Hier addieren wir 1,0 zu 2,0, was offensichtlich 3,0 ergibt.


165
Wir haben also einen Entwickler gefunden, der viel Klarheit für ein wenig Kürze geopfert hat, und hier sind wir.
TemporalWolf

11
Vielleicht speichert jemand seinen Quellcode auf einer 5,5-Zoll-Diskette?
Thomas Ayoub

10
@ ThomasAyoub es wäre 5,25 "iirc ;-)
jjmontes

9

2
Unterhaltsame Tatsache, Sie können dies auch in JavaScript tun:1..toString()
Derek 21 會 功夫

74

Die Frage ist bereits ausreichend beantwortet (dh die Antwort von @Paul Rooney ), es ist jedoch auch möglich, die Richtigkeit dieser Antworten zu überprüfen.

Lassen Sie mich die vorhandenen Antworten zusammenfassen: Das ..ist kein einzelnes Syntaxelement!

Sie können überprüfen, wie der Quellcode "tokenisiert" ist . Diese Token stellen dar, wie der Code interpretiert wird:

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

Die Zeichenfolge 1.wird also als Zahl interpretiert, die zweite .ist ein OP (ein Operator, in diesem Fall der Operator "get attribute") und __truediv__der Methodenname. Dies ist also nur ein Zugriff auf die __truediv__Methode des Floats 1.0.

Eine andere Möglichkeit, den generierten Bytecode anzuzeigen, besteht darin , ihn zusammenzusetzen. Dies zeigt tatsächlich die Anweisungen, die ausgeführt werden, wenn Code ausgeführt wird: 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

Was im Grunde das gleiche sagt. Es lädt das Attribut __truediv__der Konstante 1.0.


Was Ihre Frage betrifft

Und wie können Sie es in einer komplexeren Aussage verwenden (wenn möglich)?

Auch wenn es möglich ist, dass Sie niemals solchen Code schreiben sollten, einfach weil unklar ist, was der Code tut. Verwenden Sie es also bitte nicht in komplexeren Aussagen. Ich würde sogar so weit gehen, dass Sie es nicht in so "einfachen" Anweisungen verwenden sollten, zumindest sollten Sie die Anweisungen in Klammern trennen:

f = (1.).__truediv__

dies wäre definitiv besser lesbar - aber etwas in der Art von:

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

wäre noch besser!

Der verwendete Ansatz partialbewahrt auch das Python-Datenmodell (der 1..__truediv__Ansatz nicht!), Was durch dieses kleine Snippet demonstriert werden kann:

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

Dies liegt daran, dass 1. / (1+2j)nicht von, float.__truediv__sondern mit complex.__rtruediv__- ausgewertet wird , um operator.truedivsicherzustellen, dass die umgekehrte Operation aufgerufen wird, wenn die normale Operation zurückkehrt, NotImplementedaber Sie diese Fallbacks nicht haben, wenn Sie __truediv__direkt arbeiten. Dieser Verlust des "erwarteten Verhaltens" ist der Hauptgrund, warum Sie (normalerweise) magische Methoden nicht direkt anwenden sollten.


40

Zwei Punkte zusammen können zunächst etwas umständlich sein:

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

Aber es ist dasselbe wie beim Schreiben:

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

Weil floatLiterale in drei Formen geschrieben werden können:

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

Das ist überraschend, warum sind diese Syntax gültig, aber 1.__truediv__nicht?
Alex Hall

3
@AlexHall Siehe hier . Das .scheint als Teil der Nummer analysiert zu werden, und dann .fehlt das für den Methoden-Accessor.
tobias_k

7
Da es sich jedoch um eine umständliche und unklare Syntax handelt, sollte sie wahrscheinlich vermieden werden.
DrMcCleod

11

Was ist f = 1..__truediv__?

fist eine gebundene Spezialmethode für einen Float mit dem Wert Eins. Speziell,

1.0 / x

Ruft in Python 3 Folgendes auf:

(1.0).__truediv__(x)

Beweis:

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

und:

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

Wenn wir es tun:

f = one.__truediv__

Wir behalten einen Namen, der an diese gebundene Methode gebunden ist

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

Wenn wir diese gepunktete Suche in einer engen Schleife durchführen würden, könnte dies ein wenig Zeit sparen.

Analysieren des abstrakten Syntaxbaums (AST)

Wir können sehen, dass das Parsen des AST für den Ausdruck uns sagt, dass wir das __truediv__Attribut für die Gleitkommazahl erhalten 1.0:

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

Sie könnten dieselbe resultierende Funktion erhalten von:

f = float(1).__truediv__

Oder

f = (1.0).__truediv__

Abzug

Wir können auch durch Abzug dorthin gelangen.

Lass es uns aufbauen.

1 für sich ist ein int:

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

1 mit einem Punkt nach dem Float:

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

Der nächste Punkt an sich wäre ein SyntaxError, aber es beginnt eine gepunktete Suche auf der Instanz des Floats:

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

Niemand sonst hat dies erwähnt - Dies ist jetzt eine "gebundene Methode" auf dem Float , 1.0:

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

Wir könnten dieselbe Funktion viel besser lesbar machen:

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

Performance

Der Nachteil der divide_one_byFunktion ist, dass ein weiterer Python-Stack-Frame erforderlich ist, wodurch sie etwas langsamer als die gebundene Methode ist:

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

Wenn Sie nur einfache Literale verwenden können, ist das natürlich noch schneller:

>>> def f_3():
...     for x in range(1, 11):
...         1.0/x
...         
>>> timeit.repeat(f_3)
[2.1224895628296281, 2.1219930218637728, 2.1280188256941983]
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.