什么是 1..__truediv__`? Python 是否有 .. ("dot dot") 符号语法?

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。

两个点放在一起一开始可能有点别扭:

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

问题已经得到充分回答(即的答案),但也可以验证这些答案的正确性。

让我回顾一下现有答案:.. 不是单个语法元素!

您可以查看源代码如何"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(一个运算符,在本例中是 "get attribute" 运算符)和 __truediv__是方法名。所以这只是访问 float 1.0.

__truediv__ 方法

另一种查看生成的字节码的方法是 disassemble它。这实际上显示了执行某些代码时执行的指令:

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

关于你的问题

And how can you use it in a more complex statement (if possible)?

尽管您可能永远不应该编写那样的代码,只是因为不清楚代码的作用。所以请不要在更复杂的语句中使用它。我什至会走得太远,你不应该在 so "simple" 语句中使用它,至少你应该使用括号来分隔指令:

f = (1.).__truediv__

这肯定会更具可读性 - 但类似于:

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

会更好!

使用 partial 的方法也保留了 python's data model1..__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 确保在正常操作时调用反向操作 returns NotImplemented 但是当你直接在 __truediv__ 上操作时,你没有这些回退。 "expected behaviour" 的丢失是您(通常)不应该直接使用魔术方法的主要原因。

What is f = 1..__truediv__?

f 是一个绑定在值为 1 的浮点数上的特殊方法。具体来说,

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]