Apa itu `1 ..__ truediv__`? Apakah Python memiliki sintaksasi notasi .. (“dot dot”)?

190

Saya baru-baru ini menemukan sintaks yang tidak pernah saya lihat sebelumnya ketika saya belajar python atau dalam kebanyakan tutorial, ..notasinya, terlihat seperti ini:

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

print(f(8)) # prints 0.125 

Saya pikir itu persis sama dengan (kecuali tentu saja lebih lama):

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

Tapi pertanyaan saya adalah:

  • Bagaimana itu bisa dilakukan?
  • Apa sebenarnya yang dimaksud dengan dua titik?
  • Bagaimana Anda bisa menggunakannya dalam pernyataan yang lebih kompleks (jika mungkin)?

Ini mungkin akan menyelamatkan saya banyak baris kode di masa depan ... :)

abccd
sumber
14
Catatan: (1).__truediv__tidak benar-benar sama dengan 1..__truediv__, seperti panggilan sebelumnya int.__truediv__sedangkan yang terakhir tidak float.__truediv__. Atau, Anda juga dapat menggunakan 1 .__truediv__(dengan spasi) `
tobias_k
7
Perhatikan bahwa 1//8adalah 0, tidak 0.125, baik dalam versi Python.
mkrieger1
1
mengingatkan saya padaif (x <- 3) {...}
Entahlah
7
Ini adalah contoh dari ini yang digunakan.
Éamonn Olive
3
@KeithC Jawaban dan komentar berkualitas tinggi menunjukkan kode sampel membutuhkan wawasan untuk dipahami, mengejutkan bagi banyak orang, memiliki alternatif yang lebih jelas, lebih umum, dan setidaknya seefisien. Keluhan utama saya adalah bahwa keterbacaan penting. Simpan kepintaran untuk tempat yang paling dibutuhkan - berkomunikasi dengan manusia.
Peter Wood

Jawaban:

212

Apa yang Anda miliki adalah floatliteral tanpa nol tambahan, yang kemudian Anda akses __truediv__metode. Itu bukan operator itu sendiri; titik pertama adalah bagian dari nilai float, dan yang kedua adalah operator titik untuk mengakses properti objek dan metode.

Anda dapat mencapai titik yang sama dengan melakukan hal berikut.

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

Contoh lain

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

Di sini kita menambahkan 1,0 hingga 2,0, yang jelas menghasilkan 3,0.

Paul Rooney
sumber
165
Jadi apa yang kami temukan adalah dev yang mengorbankan banyak kejelasan untuk sedikit singkatnya dan inilah kami.
TemporalWolf
11
Mungkin seseorang menyimpan kode sumbernya ke floppy disk 5.5 "?
Thomas Ayoub
10
@ThomasAyoub itu akan menjadi 5,25 "iirc ;-)
jjmontes
9
@TemporalWolf Dia mungkin menemukannya dalam pengiriman kode golf terbaru ini .
Brian McCutchon
2
Fakta asyiknya, Anda juga bisa melakukan ini dalam JavaScript:1..toString()
Derek 朕 會 功夫
74

Pertanyaannya sudah cukup dijawab (yaitu jawaban @Paul Rooney ) tetapi juga mungkin untuk memverifikasi kebenaran jawaban ini.

Biarkan saya rekap jawaban yang ada: Ini ..bukan elemen sintaks tunggal!

Anda dapat memeriksa bagaimana kode sumber "tokenized" . Token ini menunjukkan bagaimana kode ditafsirkan:

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

Jadi string 1.ditafsirkan sebagai angka, yang kedua .adalah OP (operator, dalam hal ini operator "dapatkan atribut") dan __truediv__adalah nama metode. Jadi ini hanya mengakses __truediv__metode float 1.0.

Cara lain untuk melihat bytecode yang dihasilkan adalah dengan merakitnya . Ini sebenarnya menunjukkan instruksi yang dilakukan ketika beberapa kode dijalankan: 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

Yang pada dasarnya mengatakan hal yang sama. Ini memuat atribut __truediv__dari konstanta 1.0.


Mengenai pertanyaan Anda

Dan bagaimana Anda bisa menggunakannya dalam pernyataan yang lebih kompleks (jika mungkin)?

Meskipun mungkin Anda tidak boleh menulis kode seperti itu, hanya karena tidak jelas apa yang dilakukan kode. Jadi tolong jangan menggunakannya dalam pernyataan yang lebih kompleks. Saya bahkan akan melangkah lebih jauh sehingga Anda tidak boleh menggunakannya dalam pernyataan yang begitu "sederhana", setidaknya Anda harus menggunakan tanda kurung untuk memisahkan instruksi:

f = (1.).__truediv__

ini akan lebih mudah dibaca - tetapi sesuatu seperti:

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

akan lebih baik!

Pendekatan menggunakan partialjuga mempertahankan model data python ( 1..__truediv__pendekatan tidak!) Yang dapat ditunjukkan oleh potongan kecil ini:

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

Ini karena 1. / (1+2j)tidak dievaluasi oleh float.__truediv__tetapi dengan complex.__rtruediv__- operator.truedivmemastikan operasi mundur dipanggil ketika operasi normal kembali NotImplementedtetapi Anda tidak memiliki fallback ini ketika Anda beroperasi __truediv__secara langsung. Hilangnya "perilaku yang diharapkan" ini adalah alasan utama mengapa Anda (biasanya) tidak harus menggunakan metode sihir secara langsung.

MSeifert
sumber
40

Dua titik bersama mungkin agak canggung pada awalnya:

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

Tetapi sama dengan menulis:

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

Karena floatliteral dapat ditulis dalam tiga bentuk:

normal_float = 1.0
short_float = 1.  # == 1.0
prefixed_float = .1  # == 0.1
sobolevn
sumber
Ini mengejutkan, mengapa sintaksis ini valid tetapi 1.__truediv__tidak?
Alex Hall
3
@AlexHall Lihat di sini . The .tampaknya diurai sebagai bagian dari nomor tersebut, dan kemudian .untuk metode accessor hilang.
tobias_k
7
Tetapi karena sintaksnya canggung dan tidak jelas, mungkin harus dihindari.
DrMcCleod
11

Apa f = 1..__truediv__?

fadalah metode khusus terikat pada float dengan nilai satu. Secara khusus,

1.0 / x

dalam Python 3, aktifkan:

(1.0).__truediv__(x)

Bukti:

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

dan:

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

Jika kita melakukannya:

f = one.__truediv__

Kami mempertahankan nama yang terikat pada metode terikat itu

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

Jika kami melakukan pencarian putus-putus itu dalam satu lingkaran ketat, ini bisa menghemat sedikit waktu.

Parsing Pohon Sintaksis Abstrak (AST)

Kita bisa melihat bahwa parsing AST untuk ekspresi memberitahu kita bahwa kita mendapatkan __truediv__atribut pada jumlah floating point, 1.0:

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

Anda bisa mendapatkan fungsi hasil yang sama dari:

f = float(1).__truediv__

Atau

f = (1.0).__truediv__

Deduksi

Kita juga bisa sampai di sana dengan deduksi.

Mari kita membangunnya.

1 dengan sendirinya adalah int:

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

1 dengan periode setelahnya adalah pelampung:

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

Titik berikutnya dengan sendirinya akan menjadi SyntaxError, tapi itu memulai pencarian bertitik pada contoh float:

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

Tidak ada orang lain telah disebutkan ini - ini sekarang menjadi "metode terikat" pada float, 1.0:

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

Kita dapat melakukan fungsi yang sama dengan lebih mudah:

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

Performa

Kelemahan dari divide_one_byfungsi ini adalah bahwa ia membutuhkan bingkai tumpukan Python lain, membuatnya agak lebih lambat daripada metode terikat:

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

Tentu saja, jika Anda bisa menggunakan literal biasa, itu bahkan lebih cepat:

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