什么是 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__
方法
另一种查看生成的字节码的方法是 dis
assemble它。这实际上显示了执行某些代码时执行的指令:
>>> 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 model(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
确保在正常操作时调用反向操作 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]
我最近遇到了一个我在学习 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__
方法
另一种查看生成的字节码的方法是 dis
assemble它。这实际上显示了执行某些代码时执行的指令:
>>> 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 model(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
确保在正常操作时调用反向操作 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]