`1 ..__ truediv__` là gì? Python có cú pháp ký hiệu .. (dot dot dot) không?


190

Gần đây tôi đã bắt gặp một cú pháp mà tôi chưa từng thấy trước đây khi tôi học python cũng như trong hầu hết các hướng dẫn, ..ký hiệu, nó trông giống như thế này:

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

print(f(8)) # prints 0.125 

Tôi hình dung nó giống hệt như (tất nhiên trừ nó dài hơn):

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

Nhưng câu hỏi của tôi là:

  • Làm thế nào nó có thể làm điều đó?
  • Nó thực sự có ý nghĩa gì với hai dấu chấm?
  • Làm thế nào bạn có thể sử dụng nó trong một tuyên bố phức tạp hơn (nếu có thể)?

Điều này có thể sẽ giúp tôi tiết kiệm nhiều dòng mã trong tương lai ... :)


14
Lưu ý: (1).__truediv__không thực sự giống 1..__truediv__như các cuộc gọi trước int.__truediv__trong khi cuộc gọi sau thực hiện float.__truediv__. Ngoài ra, bạn cũng có thể sử dụng 1 .__truediv__(có dấu cách) `
tobias_k

7
Lưu ý rằng 1//80, không 0.125, trong cả hai phiên bản của Python.
mkrieger1

1
làm tôi nhớ đếnif (x <- 3) {...}
Dunno

7
Dưới đây là một ví dụ về điều này được sử dụng.
Éamonn Olive

3
@KeithC Các câu trả lời và nhận xét chất lượng cao cho thấy mã mẫu cần có cái nhìn sâu sắc để hiểu, gây ngạc nhiên cho nhiều người, có các lựa chọn thay thế rõ ràng hơn, tổng quát hơn và ít nhất là hiệu quả. Gripe chính của tôi là tính dễ đọc. Tiết kiệm thông minh cho nơi cần thiết nhất - giao tiếp với con người.
Peter Wood

Câu trả lời:


212

Những gì bạn có là một floatnghĩa đen mà không có dấu 0, sau đó bạn truy cập vào __truediv__phương thức. Bản thân nó không phải là một nhà điều hành; dấu chấm đầu tiên là một phần của giá trị float và dấu phẩy thứ hai là toán tử dấu chấm để truy cập các thuộc tính và phương thức của đối tượng.

Bạn có thể đạt được cùng một điểm bằng cách làm như sau.

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

Một vi dụ khac

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

Ở đây chúng tôi thêm 1.0 đến 2.0, rõ ràng mang lại 3.0.


165
Vì vậy, những gì chúng tôi tìm thấy là một nhà phát triển đã hy sinh rất nhiều sự rõ ràng cho một chút ngắn gọn và chúng tôi đang ở đây.
TemporalWolf

11
Có lẽ ai đó đang lưu mã nguồn của mình vào đĩa mềm 5,5 "?
Thomas Ayoub

10
@Thomas Chắc chắn sẽ là 5,25 "iirc ;-)
jjmontes

9
@TemporalWolf Anh ấy có thể đã tìm thấy nó trong bài nộp golf mã gần đây .
Brian McCutchon

2
Thực tế thú vị, bạn cũng có thể làm điều này trong JavaScript:1..toString()
Derek 會

74

Câu hỏi đã được trả lời đầy đủ (ví dụ: câu trả lời của @Paul Rooney ) nhưng cũng có thể xác minh tính chính xác của những câu trả lời này.

Hãy để tôi tóm tắt lại các câu trả lời hiện có: Đây ..không phải là một yếu tố cú pháp duy nhất!

Bạn có thể kiểm tra cách mã nguồn được "mã hóa" . Các mã thông báo này đại diện cho cách mã được diễn giải:

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

Vì vậy, chuỗi 1.được hiểu là số, thứ hai .là OP (toán tử, trong trường hợp này là toán tử "get property") và __truediv__là tên phương thức. Vì vậy, đây chỉ là truy cập vào __truediv__phương pháp của float 1.0.

Một cách khác để xem mã byte được tạo là lắp ráp nó. Điều này thực sự hiển thị các hướng dẫn được thực hiện khi một số mã được thực thi: 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

Mà về cơ bản cũng nói như vậy. Nó tải thuộc tính __truediv__của hằng 1.0.


Liên quan đến câu hỏi của bạn

Và làm thế nào bạn có thể sử dụng nó trong một tuyên bố phức tạp hơn (nếu có thể)?

Mặc dù có thể bạn không bao giờ nên viết mã như vậy, đơn giản vì không rõ mã đang làm gì. Vì vậy, xin vui lòng không sử dụng nó trong các tuyên bố phức tạp hơn. Tôi thậm chí sẽ đi xa đến mức bạn không nên sử dụng nó trong các câu lệnh "đơn giản" như vậy, ít nhất bạn nên sử dụng dấu ngoặc đơn để phân tách các hướng dẫn:

f = (1.).__truediv__

điều này chắc chắn sẽ dễ đọc hơn - nhưng một cái gì đó dọc theo dòng:

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

sẽ còn tốt hơn nữa

Cách tiếp cận sử dụng partialcũng bảo tồn mô hình dữ liệu của python ( 1..__truediv__cách tiếp cận không!) Có thể được chứng minh bằng đoạn trích nhỏ này:

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

Điều này là do 1. / (1+2j)không được đánh giá bởi float.__truediv__nhưng với complex.__rtruediv__- operator.truedivđảm bảo rằng thao tác ngược được gọi khi hoạt động bình thường trở lại NotImplementednhưng bạn không có những dự phòng này khi bạn thao tác __truediv__trực tiếp. Mất "hành vi dự kiến" này là lý do chính tại sao bạn (thông thường) không nên sử dụng phương pháp ma thuật trực tiếp.


40

Hai dấu chấm với nhau có thể hơi khó xử lúc đầu:

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

Nhưng nó cũng giống như viết:

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

Bởi vì nghĩa floatđen có thể được viết dưới ba hình thức:

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

Điều này là đáng ngạc nhiên, tại sao những cú pháp hợp lệ nhưng 1.__truediv__không?
Alex Hall

3
@AlexHall Xem tại đây . Các .dường như được phân tích như một phần của số lượng, và sau đó là .đối với phương pháp accessor là mất tích.
tobias_k

7
Nhưng vì nó là cú pháp vụng về và không rõ ràng, nên có lẽ nên tránh.
DrMcCleod

11

f = 1..__truediv__

flà một phương pháp đặc biệt ràng buộc trên một float với giá trị là một. Đặc biệt,

1.0 / x

trong Python 3, gọi:

(1.0).__truediv__(x)

Chứng cớ:

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

và:

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

Nếu chúng ta làm:

f = one.__truediv__

Chúng tôi giữ lại một tên ràng buộc với phương thức ràng buộc đó

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

Nếu chúng ta đang thực hiện tra cứu rải rác đó trong một vòng lặp chặt chẽ, điều này có thể tiết kiệm một ít thời gian.

Phân tích cú pháp cây cú pháp trừu tượng (AST)

Chúng ta có thể thấy rằng phân tích AST cho biểu thức cho chúng ta biết rằng chúng ta đang nhận được __truediv__thuộc tính trên số dấu phẩy động , 1.0:

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

Bạn có thể nhận được chức năng kết quả tương tự từ:

f = float(1).__truediv__

Hoặc là

f = (1.0).__truediv__

Khấu trừ

Chúng tôi cũng có thể đến đó bằng cách khấu trừ.

Hãy xây dựng nó lên.

1 bởi chính nó là một int:

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

1 với một khoảng thời gian sau khi nó là một float:

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

Dấu chấm tiếp theo tự nó sẽ là một SyntaxError, nhưng nó bắt đầu một tra cứu chấm trên trường hợp của float:

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

Không ai khác đã đề cập đến điều này - Đây hiện là một "phương thức ràng buộc" trên float , 1.0:

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

Chúng ta có thể thực hiện cùng chức năng dễ đọc hơn nhiều:

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

Hiệu suất

Nhược điểm của divide_one_byhàm là nó yêu cầu một khung stack Python khác, làm cho nó chậm hơn một chút so với phương thức ràng buộc:

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

Tất nhiên, nếu bạn chỉ có thể sử dụng chữ đơn giản, điều đó thậm chí còn nhanh hơn:

>>> def f_3():
...     for x in range(1, 11):
...         1.0/x
...         
>>> timeit.repeat(f_3)
[2.1224895628296281, 2.1219930218637728, 2.1280188256941983]
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.