Що таке `1 ..__ truediv__`? Чи є в Python синтаксис позначень .. ("dot dot")?


190

Нещодавно я натрапив на синтаксис, якого я ніколи не бачив, коли я вивчив пітон, ні в більшості підручників, ..позначення, це виглядає приблизно так:

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

Але мої запитання:

  • Як це можна зробити?
  • Що це насправді означає дві точки?
  • Як ви можете використовувати його у більш складному висловлюванні (якщо можливо)?

Це, ймовірно, врятує мені багато рядків коду в майбутньому ... :)


14
Примітка: (1).__truediv__насправді це не те саме, що 1..__truediv__, як колишні дзвінки, int.__truediv__а другі float.__truediv__. Крім того, ви також можете використовувати 1 .__truediv__(з пробілом) `
tobias_k

7
Зауважте, що 1//8це 0не так 0.125, ні в одній з версій Python.
mkrieger1

1
нагадує меніif (x <- 3) {...}
Данно

7
Ось приклад цього у використанні.
Еймонн Олив

3
@KeithC Високоякісні відповіді та коментарі показують, що зразок коду потребує розуміння, для багатьох це дивує, є альтернативи, які більш чіткі, загальні та принаймні ефективні. Моя основна суть полягає в тому, що читабельність враховується. Збережіть кмітливість там, де це найбільше потрібно - спілкування з людьми.
Пітер Вуд

Відповіді:


212

У вас є floatбуквальне значення без остаточного нуля, до якого ви отримуєте доступ до __truediv__методу. Це сам по собі не оператор; перша точка є частиною значень float, а друга - оператором точок для доступу до властивостей та методів об'єктів.

Ви можете досягти тієї ж точки, виконавши наступне.

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


165
Тож, що ми знайшли, - це чорт, який пожертвував великою кількістю ясності для трохи стислості, і ось ми.
TemporalWolf

11
Можливо, хтось зберігає свій вихідний код на 5,5 "дискети?
Thomas Ayoub

10
@ThomasAyoub це було б 5,25 "iirc ;-)
jjmontes


2
Цікаво, що ви також можете це зробити в JavaScript:1..toString()
Дерек 朕 會 功夫

74

На питання вже досить відповіді (тобто відповідь @Paul Rooney ), але також можна перевірити правильність цих відповідей.

Дозвольте мені резюмувати наявні відповіді: The ..- це не один елемент синтаксису!

Ви можете перевірити, як "токенізований" вихідний код . Ці лексеми представляють, як інтерпретується код:

>>> 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.інтерпретується як число, другий .- це ОП (оператор, в даному випадку оператор "отримати атрибут"), а " __truediv__ім'я методу". Отже, це лише доступ до __truediv__методу поплавця 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__безпосередньо. Ця втрата "очікуваної поведінки" є основною причиною, чому ви (як правило) не повинні використовувати магічні методи безпосередньо.


40

Дві крапки разом можуть бути трохи незручними спочатку:

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

Це дивно, чому ці дійсні синтаксиси 1.__truediv__є, але це не так?
Алекс Холл

3
@AlexHall Дивіться тут . ., Як представляється, аналізуються як частина номера, а потім .для методу доступу відсутні.
tobias_k

7
Але оскільки це незручний і незрозумілий синтаксис, його, мабуть, слід уникати.
DrMcCleod

11

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

f- пов'язаний спеціальний метод на поплавці зі значенням одиниці. Зокрема,

1.0 / x

в Python 3 викликає:

(1.0).__truediv__(x)

Докази:

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

і:

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

Якщо ми робимо:

f = one.__truediv__

Ми зберігаємо ім'я, пов'язане з цим зв'язаним методом

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

Якби ми робили цю точку пошуку в тісному циклі, це може заощадити трохи часу.

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

Ми можемо бачити, що аналіз 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>

Ніхто більше не згадував про це. Це тепер "зв'язаний метод" на поплавці 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

Продуктивність

Мінус divide_one_byфункції полягає в тому, що він вимагає іншого кадру стека 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]
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.