`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ในรุ่นของงูใหญ่อย่างใดอย่างหนึ่ง
mkrieger1

1
ทำให้ฉันนึกถึงif (x <- 3) {...}
Dunno

7
นี่คือตัวอย่างของการใช้งานนี้
Éamonn Olive

3
@ KeithC คำตอบและความคิดเห็นที่มีคุณภาพสูงแสดงให้เห็นว่าโค้ดตัวอย่างต้องการความเข้าใจอย่างลึกซึ้งเป็นที่น่าประหลาดใจสำหรับหลาย ๆ คนมีทางเลือกที่ชัดเจนยิ่งขึ้นทั่วไปและอย่างน้อยก็มีประสิทธิภาพ Gripe หลักของฉันคือการนับที่สามารถอ่านได้ ประหยัดความฉลาดสำหรับสถานที่ที่ต้องการมากที่สุด - สื่อสารกับมนุษย์
Peter Wood

คำตอบ:


212

สิ่งที่คุณมีคือ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


165
ดังนั้นสิ่งที่เราพบคือนักพัฒนาที่เสียสละความชัดเจนมากมายเพื่อความกะทัดรัดและที่นี่เรา
TemporalWolf

11
อาจจะมีคนที่มีการบันทึกรหัสที่มาของเขาไป 5.5" ฟล็อปปี้ดิสก์?
โทมัส Ayoub

10
@ThomasAyoub เป็น 5.25 "iirc ;-)
jjmontes

9
@TemporalWolf เขาอาจจะได้พบมันในการส่งรหัสกอล์ฟนี้ที่ผ่านมา
Brian McCutchon

2
สนุกจริงๆคุณยังสามารถทำได้ใน JavaScript:1..toString()
Derek 朕會功夫

74

คำถามดังกล่าวได้รับการตอบอย่างเพียงพอแล้ว (เช่น@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 (ตัวดำเนินการในกรณีนี้ตัวดำเนินการ "รับแอตทริบิวต์") และ__truediv__เป็นชื่อวิธี นี่คือการเข้าถึง__truediv__วิธีการลอย1.0วิธีการลอย

วิธีการดู bytecode สร้างก็คือการรวบรวมมัน นี่แสดงให้เห็นถึงคำแนะนำที่ใช้จริงเมื่อประมวลผลรหัสบางอย่าง: 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ยังรักษารูปแบบข้อมูลของงูใหญ่ ( 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__ไม่ใช่?
Alex Hall

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 แต่มันเริ่มต้นการค้นหาจุดบนอินสแตนซ์ของการลอย:

>>> 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]
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.