【问题标题】:What is `1..__truediv__` ? Does Python have a .. ("dot dot") notation syntax?什么是`1..__truediv__`? Python 是否有 .. ("dot dot") 表示法语法?
【发布时间】:2017-09-15 05:10:44
【问题描述】:

我最近遇到了一种我在学习 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

但我的问题是:

  • 它怎么能做到这一点?
  • 这两个点的实际含义是什么?
  • 如何在更复杂的语句中使用它(如果可能)?

这可能会在未来为我节省很多代码行...:)

【问题讨论】:

  • 注意:(1).__truediv__1..__truediv__ 并不完全相同,因为前者调用int.__truediv__ 而后者调用float.__truediv__。或者,也可以使用1 .__truediv__(带空格)`
  • 请注意,1//80,而不是 0.125,在任一版本的 Python 中。
  • 让我想起了if (x <- 3) {...}
  • Here 就是一个正在使用的例子。
  • @KeithC 高质量的答案和 cmets 表明示例代码需要洞察力才能理解,令许多人感到惊讶,具有更清晰、更通用且至少同样有效的替代方案。我的主要抱怨是可读性很重要。把聪明才智留到最需要的地方——与人交流。

标签: python python-3.x syntax operators python-2.x


【解决方案1】:

您拥有的是一个没有尾随零的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。

【讨论】:

  • 所以我们发现了一个为了简洁而牺牲了很多清晰度的开发人员,我们就在这里。
  • 也许有人将他的源代码保存到 5.5" 软盘上?
  • @ThomasAyoub 应该是 5.25" iirc ;-)
  • @TemporalWolf 他可能在this recent code golf submission找到了它。
  • 有趣的是,您也可以在 JavaScript 中执行此操作:1..toString()
【解决方案2】:

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

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

您可以查看源代码"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__ 是方法名。所以这只是访问浮动1.0__truediv__方法。

查看生成的字节码的另一种方式是to 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__


关于你的问题

你如何在更复杂的语句中使用它(如果可能的话)?

即使您可能永远不应该编写这样的代码,只是因为不清楚代码在做什么。所以请不要在更复杂的语句中使用它。我什至会说你不应该在如此“简单”的语句中使用它,至少你应该使用括号来分隔说明:

f = (1.).__truediv__

这无疑更具可读性 - 但类似于:

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

会更好!

使用partial 的方法还保留了python's data model1..__truediv__ 方法没有!)这可以通过这个小sn-p 来演示:

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

【讨论】:

    【解决方案3】:

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

    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
    

    【讨论】:

    • 这很奇怪,为什么这些语法有效但1.__truediv__ 不是?
    • @AlexHall 见here. 似乎被解析为数字的一部分,然后方法访问器的 . 丢失了。
    • 但由于它的语法笨拙且不清楚,因此应该避免使用。
    【解决方案4】:

    什么是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 告诉我们,我们得到了浮点数 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__
    

    扣除

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

    让我们建立起来吧。

    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
    

    我们可以更易读地完成相同的功能:

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

    【讨论】:

      猜你喜欢
      • 2012-03-17
      • 1970-01-01
      • 1970-01-01
      • 2010-11-15
      • 1970-01-01
      • 2013-01-20
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多