Что такое синтаксис обозначения python.. ( "dot dot" )?

Недавно я столкнулся с синтаксисом, который я никогда раньше не видел, когда изучал питон и в большинстве руководств, нотацию .., он выглядит примерно так:

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

print(f(8)) # prints 0.125 

Я понял, что это точно так же (за исключением, конечно, продолжительного):

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

Но мои вопросы:

  • Как это можно сделать?
  • Что это значит для двух точек?
  • Как вы можете использовать его в более сложном выражении (если это возможно)?

Это, вероятно, сохранит мне много строк кода в будущем...:)

Ответ 1

То, что у вас есть, - это литерал float без конечного нуля, к которому вы затем обращаетесь к методу __truediv__. Это не сам оператор; первая точка является частью значения поплавка, а вторая является оператором точек для доступа к свойствам и методам объектов.

Вы можете достичь одной и той же точки, выполнив следующие действия.

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

Другой пример

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

Здесь мы добавим 1.0 к 2.0, что, очевидно, дает 3.0.

Ответ 2

Вопрос уже достаточно отвечен (т.е. @Paul Rooney), но также можно проверить правильность этих ответов.

Позвольте мне повторить существующие ответы: .. не является одним синтаксическим элементом!

Вы можете проверить, как исходный код "tokenized" . Эти токены представляют собой интерпретацию кода:

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

Итак, строка 1. интерпретируется как число, вторая . - это OP (оператор, в этом случае оператор get get), а __truediv__ - имя метода. Таким образом, это просто доступ к методу __truediv__ для float 1.0.

Другой способ просмотра сгенерированного байт-кода - 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

Что в основном говорит то же самое. Он загружает атрибут __truediv__ константы 1.0.


Относительно вашего вопроса

И как вы можете использовать его в более сложном выражении (если это возможно)?

Даже если это возможно, вы никогда не должны писать такой код, просто потому, что неясно, что делает код. Поэтому, пожалуйста, не используйте его в более сложных заявлениях. Я бы даже зашел так далеко, что вы не должны использовать его в таких "простых" утверждениях, по крайней мере, вы должны использовать скобки для разделения инструкций:

f = (1.).__truediv__

это было бы более читаемо, но что-то вроде:

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

будет еще лучше!

Подход с использованием partial также сохраняет модель данных python (подход 1..__truediv__ не работает!), что может быть продемонстрировано этим маленьким фрагмент кода:

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

Это связано с тем, что 1. / (1+2j) не оценивается float.__truediv__, но при complex.__rtruediv__ - operator.truediv выполняется обратная операция, когда нормальная операция возвращает NotImplemented, но у вас нет этих резервных копий, когда вы непосредственно на __truediv__. Эта потеря "ожидаемого поведения" является основной причиной, по которой вы (обычно) не должны использовать магические методы напрямую.

Ответ 3

Две точки вместе могут быть немного неудобными сначала:

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

Но это то же самое, что и запись:

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

Поскольку литералы float могут быть записаны в трех формах:

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

Ответ 4

Что такое f = 1..__truediv__?

Разбор абстрактного дерева синтаксиса (AST)

Мы видим, что анализ АСТ для выражения говорит нам, что мы получаем атрибут __truediv__ для числа с плавающей запятой, 1.0:

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

Вы можете получить одну и ту же результирующую функцию:

f = float(1).__truediv__

или

f = (1.0).__truediv__

Вычитание

Мы также можем получить это путем вывода.

Позвольте создать его.

1 сам по себе является int:

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

1 с периодом после его поплавка:

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

Следующая точка сама по себе будет SyntaxError, но она начнет пунктирный поиск в экземпляре float:

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

Никто не упомянул об этом - теперь это привязанный метод " на float, 1.0:

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

Мы могли бы выполнить ту же функцию гораздо более читаемо:

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

Производительность

Недостатком функции является то, что она требует другого фрейма стека Python, делая его несколько медленнее, чем связанный метод:

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

Конечно, если вы можете просто использовать простые литералы, это еще быстрее:

>>> def f_3():
...     for x in range(1, 11):
...         1.0/x
...         
>>> timeit.repeat(f_3)
[2.1224895628296281, 2.1219930218637728, 2.1280188256941983]