什么是python ..(“dot dot”)符号语法?

我最近遇到了一个我从来没有见过的语法,当我学习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的答案),但是也可以validation这些答案的正确性。

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

您可以检查源代码是如何“标记化”的 。 这些令牌表示如何解释代码:

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

所以string1.被解释为数字,第二个. 是一个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__


关于你的问题

你怎么能用更复杂的语句(如果可能的话)?

尽pipe有可能你不应该这样写代码,只是因为不清楚代码在做什么。 所以请不要在更复杂的语句中使用它。 我甚至可以走得这么远,以至于你不应该在如此“简单”的陈述中使用它,至less你应该用括号来分隔指令:

 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____truediv__ 。 这种“预期行为”的损失是你(通常)不应该直接使用魔法的主要原因。

起初两个点可能有些尴尬:

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

但是写作是一样的:

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

因为float文字可以写成三种forms:

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

什么是f = 1..__truediv__

parsing抽象语法树(AST)

我们可以看到,parsingexpression式的AST告诉我们,我们正在浮点数1.0上得到__truediv__属性:

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

扣除

我们也可以通过扣除到达那里。

让我们来build立它。

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 

我们可以更好地完成同样的function:

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

性能

该函数的缺点是它需要另一个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]