Czym jest python. składnia notacji?

Ostatnio natknąłem się na składnię, której nigdy wcześniej nie widziałem, gdy uczyłem się Pythona, ani w większości samouczków, notacji .., wygląda to mniej więcej tak:

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

print(f(8)) # prints 0.125 

Pomyślałem, że to dokładnie to samo co (z tym, że jest dłuższe, oczywiście):

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

Ale moje pytania to:

    Jak to możliwe?
  • Co to właściwie znaczy z tymi dwoma kropkami?
  • Jak możesz użyć go w bardziej złożonym oświadczeniu (jeśli to możliwe)?

To prawdopodobnie zaoszczędź mi wiele linijek kodu w przyszłości...:)

Author: Paul Rooney, 2017-04-19

4 answers

To, co masz, to float literał bez końcowego zera, który następnie uzyskujesz dostęp do metody __truediv__. Nie jest operatorem samym w sobie; pierwsza kropka jest częścią wartości float, a druga operatorem kropki, aby uzyskać dostęp do właściwości obiektów i metod.

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

Tutaj dodajemy 1.0 do 2.0, co oczywiście daje 3.0.

 212
Author: Paul Rooney,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-04-19 22:56:58

Pytanie jest już wystarczająco odpowiedzi (tj. @Paul Rooney s odpowiedź), ale możliwe jest również zweryfikowanie poprawności tych odpowiedzi.

Pozwolę sobie podsumować istniejące odpowiedzi: .. nie jest pojedynczym elementem składni!

Możesz sprawdzić, jak kod źródłowy jest "tokenizowany" . Te tokeny reprezentują 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__'),
 ...]

Tak więc łańcuch 1. jest interpretowany jako liczba, drugi . jest operatorem OP (w tym przypadku operator" get atrybut"), a {[8] } jest nazwą metody. Jest to więc po prostu dostęp do metody __truediv__ float 1.0.

Innym sposobem przeglądania wygenerowanego kodu bajtowego jest diszmontować to. To faktycznie pokazuje instrukcje, które są wykonywane, gdy jakiś kod jest wykonywany:

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

Który w zasadzie mówi to samo. Wczytuje atrybut __truediv__ stałej 1.0.


Odnośnie Twojego pytania

I jak można używać to w bardziej złożonym oświadczeniu (jeśli to możliwe)?

Nawet jeśli jest to możliwe, nigdy nie powinieneś pisać kodu w ten sposób, po prostu dlatego, że nie jest jasne, co ten kod robi. Więc proszę, nie używaj go w bardziej złożonych wypowiedziach. Ja bym nawet posunął się tak daleko, że nie powinieneś używać go w tak "prostych" wypowiedziach, przynajmniej powinieneś użyć nawiasu, aby oddzielić instrukcje: {]}

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 nawet lepiej!

Podejście wykorzystujące partial zachowuje również model danych Pythona (podejście 1..__truediv__ nie!), co może być zademonstrowane przez ten mały fragment:

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

Dzieje się tak dlatego, że {[16] } nie jest oceniana przez float.__truediv__, ale z complex.__rtruediv__ - operator.truediv upewnij się, że operacja odwrotna jest wywoływana, gdy normalna operacja zwraca NotImplemented, ale nie masz tych awaryjnych, gdy operujesz bezpośrednio na __truediv__. Ta utrata "oczekiwanego zachowania" jest głównym powodem, dla którego (normalnie) nie należy używać metod magicznych bezpośrednio.

 73
Author: MSeifert,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-04-21 10:35:19

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

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

Ale to to samo co pisanie:

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

Ponieważ float literały można zapisać w trzech formach:

normal_float = 1.0
short_float = 1.  # == 1.0
prefixed_float = .1  # == 0.1
 40
Author: sobolevn,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-04-19 07:55:09

Co to jest f = 1..__truediv__?

f jest powiązaną metodą specjalną na floacie o wartości 1. W szczególności

1.0 / x

W Pythonie 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ę związaną z tą metodą

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

Gdybyśmy robili to przeszukiwanie kropkami w ciasnej pętli, zaoszczędziłoby to trochę czasu.

Analiza abstrakcyjnego drzewa składniowego (AST)

Możemy zobacz, że parsowanie AST dla wyrażenia mówi nam, że otrzymujemy atrybut __truediv__ na liczbie 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żna uzyskać tę samą funkcję wynikową z:

f = float(1).__truediv__

Lub

f = (1.0).__truediv__

Dedukcja

Możemy również dostać się tam przez dedukcję.

Zbudujmy to.

1 samo w sobie jest int:

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

1 z kropką po to jest float:

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

Następna kropka sama w sobie byłaby błędem składniowym, jednak zaczyna się przeszukiwanie kropkowane na instancji float:

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

Nikt inny nie wspomniał o tym - jest to teraz "metoda związana" na floacie, 1.0:

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

Moglibyśmy osiągnąć tę samą funkcję o wiele bardziej czytelnie:

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

Wydajność

Minusem funkcji divide_one_by jest to, że wymaga innej ramki stosu Pythona, co czyni ją nieco wolniejszą niż metoda bound:

>>> 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 użyć po prostu literały, to jeszcze szybciej:

>>> def f_3():
...     for x in range(1, 11):
...         1.0/x
...         
>>> timeit.repeat(f_3)
[2.1224895628296281, 2.1219930218637728, 2.1280188256941983]
 11
Author: Aaron Hall,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-01-04 00:41:46