Co to jest „1 ..__ truediv__”? Czy Python ma składnię notacji .. („kropka”)?

190

Ostatnio natknąłem się na składnię, której nigdy wcześniej nie widziałem, kiedy nauczyłem się języka Python, ani w większości samouczków. ..Notacja wygląda tak:

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

print(f(8)) # prints 0.125 

Uznałem, że jest dokładnie taki sam jak (z tym, że jest oczywiście dłuższy):

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

Ale moje pytania to:

  • Jak to zrobić?
  • Co to właściwie oznacza dwie kropki?
  • Jak możesz użyć tego w bardziej złożonym zestawieniu (jeśli to możliwe)?

To prawdopodobnie uratuje mi wiele linii kodu w przyszłości ... :)

abccd
źródło
14
Uwaga: (1).__truediv__tak naprawdę nie jest tym samym 1..__truediv__, co poprzednie wywołania, int.__truediv__podczas gdy drugie float.__truediv__. Możesz też użyć 1 .__truediv__(ze spacją) `
tobias_k
7
Należy pamiętać, że 1//8to 0nie 0.125w jednej wersji Pythona.
mkrieger1
1
przypomina mi oif (x <- 3) {...}
Dunno
7
Oto przykład tego w użyciu.
Éamonn Olive
3
@KeithC Odpowiedzi i komentarze wysokiej jakości pokazują, że przykładowy kod wymaga zrozumienia, jest zaskakujący dla wielu, ma alternatywy, które są jaśniejsze, bardziej ogólne i co najmniej tak samo wydajne. Główną przeszkodą jest to, że liczy się czytelność. Oszczędzaj spryt tam, gdzie jest najbardziej potrzebny - komunikując się z ludźmi.
Peter Wood

Odpowiedzi:

212

To, co masz, to floatliterał bez końcowego zera, do którego następnie uzyskujesz dostęp do __truediv__metody. Sam w sobie nie jest operatorem; pierwsza kropka jest częścią wartości zmiennoprzecinkowej, a druga to operator kropki, który ma dostęp do właściwości i metod obiektów.

Możesz osiągnąć ten sam punkt, wykonując następujące czynności.

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

Inny przykład

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

Dodajemy tutaj 1,0 do 2,0, co oczywiście daje 3.0.

Paul Rooney
źródło
165
Znaleźliśmy więc programistę, który poświęcił wiele jasności za odrobinę zwięzłości i oto jesteśmy.
TemporalWolf
11
Może ten ktoś zapisuje swój kod źródłowy na dyskietce 5.5 "?
Thomas Ayoub
10
@ThomasAyoub będzie 5.25 "iirc ;-)
jjmontes
9
@TemporalWolf Być może znalazł go w tym ostatnim zgłoszeniu do gry w golfa .
Brian McCutchon,
2
Ciekawostka, możesz to również zrobić w JavaScript:1..toString()
Derek 功夫 會 功夫
74

Odpowiedź na pytanie jest już wystarczająca (tj. Odpowiedź @ Paula Rooneya ), ale można również zweryfikować poprawność tych odpowiedzi.

Pozwól, że podsumuję istniejące odpowiedzi: To ..nie jest pojedynczy element składniowy!

Możesz sprawdzić, w jaki sposób kod źródłowy jest „tokenizowany” . Te tokeny przedstawiają sposób interpretacji kodu:

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

Więc ciąg 1.interpretowany jest jako liczba, drugim .jest OP (operator, w tym przypadku operator „get atrybut”), a __truediv__nazwa metody. To tylko dostęp do __truediv__metody float 1.0.

Innym sposobem przeglądania wygenerowanego kodu bajtowego jest jego złożenie . To faktycznie pokazuje instrukcje, które są wykonywane, gdy wykonywany jest jakiś kod: 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

Co w zasadzie mówi to samo. Ładuje atrybut __truediv__stałej 1.0.


Jeśli chodzi o twoje pytanie

I jak możesz go użyć w bardziej złożonym zestawieniu (jeśli to możliwe)?

Chociaż jest to możliwe, nigdy nie powinieneś pisać takiego kodu, po prostu dlatego, że nie jest jasne, co robi kod. Dlatego nie używaj go w bardziej złożonych instrukcjach. Posunąłbym się nawet tak daleko, że nie powinieneś używać go w tak „prostych” instrukcjach, przynajmniej powinieneś użyć nawiasu do rozdzielenia instrukcji:

f = (1.).__truediv__

byłoby to zdecydowanie bardziej czytelne - ale coś w stylu:

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

byłoby jeszcze lepiej!

Podejście wykorzystujące partialzachowuje również model danych Pythona ( 1..__truediv__podejście nie!), Co można zademonstrować za pomocą tego małego fragmentu:

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

Wynika to z tego, że 1. / (1+2j)nie jest analizowane przez, float.__truediv__ale z complex.__rtruediv__- operator.truedivupewnia się, że operacja odwrotna jest wywoływana, gdy powróci normalna operacja, NotImplementedale nie wystąpią te awarie, gdy operujesz __truediv__bezpośrednio. Ta utrata „oczekiwanego zachowania” jest głównym powodem, dla którego (zwykle) nie powinieneś bezpośrednio używać magicznych metod.

MSeifert
źródło
40

Dwie kropki razem mogą początkowo być trochę niezręczne:

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

Ale to jest tak samo jak pisanie:

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

Ponieważ floatliterały można pisać w trzech formach:

normal_float = 1.0
short_float = 1.  # == 1.0
prefixed_float = .1  # == 0.1
sobolevn
źródło
To zaskakujące, dlaczego są to poprawne składnie, ale 1.__truediv__tak nie jest?
Alex Hall,
3
@AlexHall Zobacz tutaj . .Wydaje się być analizowane jako część numeru, a następnie .na metodzie akcesor brakuje.
tobias_k
7
Ale ponieważ jest to niewygodna i niejasna składnia, należy go prawdopodobnie unikać.
DrMcCleod
11

Co to jest f = 1..__truediv__?

fjest specjalną metodą związaną na liczbach zmiennoprzecinkowych o wartości jeden. Konkretnie,

1.0 / x

w Python 3 wywołuje:

(1.0).__truediv__(x)

Dowód:

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

i:

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

Jeśli zrobimy:

f = one.__truediv__

Zachowujemy nazwę powiązaną z tą powiązaną metodą

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

Gdybyśmy robili to kropkowane wyszukiwanie w ciasnej pętli, mogłoby to zaoszczędzić trochę czasu.

Analiza składni abstrakcyjnego drzewa składni (AST)

Widzimy, że parsowanie wartości AST dla wyrażenia mówi nam, że otrzymujemy __truediv__atrybut liczby zmiennoprzecinkowej 1.0:

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

Możesz uzyskać tę samą funkcję wynikową z:

f = float(1).__truediv__

Lub

f = (1.0).__truediv__

Odliczenie

Możemy się też tam dostać przez odliczenie.

Zbudujmy to.

1 sam w sobie jest int:

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

1 z kropką po nim jest liczbą zmiennoprzecinkową:

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

Następna kropka sama w sobie byłaby błędem SyntaxError, ale zaczyna kropkowane wyszukiwanie w instancji float:

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

Nikt inny o tym nie wspominał - jest to teraz „metoda związana” na float 1.0:

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

Tę samą funkcję moglibyśmy wykonać znacznie bardziej czytelnie:

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

Występ

Minusem tej divide_one_byfunkcji jest to, że wymaga ona innej ramki stosu Pythona, co czyni ją nieco wolniejszą niż metoda związana:

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

Oczywiście, jeśli możesz po prostu używać zwykłych literałów, jest to jeszcze szybsze:

>>> def f_3():
...     for x in range(1, 11):
...         1.0/x
...         
>>> timeit.repeat(f_3)
[2.1224895628296281, 2.1219930218637728, 2.1280188256941983]
Aaron Hall
źródło