簡體   English   中英

什么是`1 ..__ truediv__`? Python有一個......(“點點”)表示法語法嗎?

[英]What is `1..__truediv__` ? Does Python have a .. (“dot dot”) notation syntax?

我最近遇到了一個我以前從未見過的語法,當我學習python時,也沒有在大多數教程中使用..符號,它看起來像這樣:

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

但我的問題是:

  • 它怎么能這樣做?
  • 這兩個點實際上意味着什么?
  • 如何在更復雜的聲明中使用它(如果可能)?

這可能會在將來為我節省很多代碼...... :)

你有一個沒有尾隨零的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。

問題已經得到了充分的回答(即@Paul Rooney的答案),但也可以驗證這些答案的正確性。

讓我回顧一下現有的答案: ..不是單一的語法元素!

您可以檢查源代碼是如何“標記化”的 這些標記表示代碼的解釋方式:

>>> 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 attribute”運算符), __truediv__是方法名稱。 所以這只是訪問float 1.0__truediv__方法。

查看生成的字節碼的另一種方法是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

基本上說的一樣。 它加載常量1.0的屬性__truediv__


關於你的問題

如何在更復雜的陳述中使用它(如果可能的話)?

盡管有可能你永遠不應該編寫這樣的代碼,只是因為它不清楚代碼在做什么。 所以請不要在更復雜的陳述中使用它。 我甚至會走得太遠以至於你不應該在如此“簡單”的陳述中使用它,至少你應該使用括號來分離指令:

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__進行操作。 這種“預期行為”的喪失是你(通常)不應該直接使用魔法的主要原因。

兩個點在一起起初可能有點尷尬:

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

什么是f = 1..__truediv__

f是一個值為1的float上的綁定特殊方法。 特別,

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]

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM