简体   繁体   English

什么是`1 ..__ truediv__`? Python有一个......(“点点”)表示法语法吗?

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

I recently came across a syntax I never seen before when I learned python nor in most tutorials, the .. notation, it looks something like this: 我最近遇到了一个我以前从未见过的语法,当我学习python时,也没有在大多数教程中使用..符号,它看起来像这样:

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

print(f(8)) # prints 0.125 

I figured it was exactly the same as (except it's longer, of course): 我认为它完全相同(当然除了它更长):

f = lambda x: (1).__truediv__(x)
print(f(8)) # prints 0.125 or 1//8

But my questions are: 但我的问题是:

  • How can it do that? 它怎么能这样做?
  • What does it actually mean with the two dots? 这两个点实际上意味着什么?
  • How can you use it in a more complex statement (if possible)? 如何在更复杂的声明中使用它(如果可能)?

This will probably save me many lines of code in the future...:) 这可能会在将来为我节省很多代码...... :)

What you have is a float literal without the trailing zero, which you then access the __truediv__ method of. 你有一个没有尾随零的float文字,然后你可以访问__truediv__方法。 It's not an operator in itself; 它本身不是一个运营商; the first dot is part of the float value, and the second is the dot operator to access the objects properties and methods. 第一个点是浮点值的一部分,第二个点是访问对象属性和方法的点运算符。

You can reach the same point by doing the following. 您可以通过执行以下操作达到相同的目标。

>>> f = 1.
>>> f
1.0
>>> f.__floordiv__
<method-wrapper '__floordiv__' of float object at 0x7f9fb4dc1a20>

Another example 另一个例子

>>> 1..__add__(2.)
3.0

Here we add 1.0 to 2.0, which obviously yields 3.0. 这里我们添加1.0到2.0,显然产生3.0。

The question is already sufficiently answered (ie @Paul Rooney s answer) but it's also possible to verify the correctness of these answers. 问题已经得到了充分的回答(即@Paul Rooney的答案),但也可以验证这些答案的正确性。

Let me recap the existing answers: The .. is not a single syntax element! 让我回顾一下现有的答案: ..不是单一的语法元素!

You can check how the source code is "tokenized" . 您可以检查源代码是如何“标记化”的 These tokens represent how the code is interpreted: 这些标记表示代码的解释方式:

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

So the string 1. is interpreted as number, the second . 所以字符串1.被解释为数字,第二个. is an OP (an operator, in this case the "get attribute" operator) and the __truediv__ is the method name. 是一个OP(一个运算符,在本例中是“get attribute”运算符), __truediv__是方法名称。 So this is just accessing the __truediv__ method of the float 1.0 . 所以这只是访问float 1.0__truediv__方法。

Another way of viewing the generated bytecode is to dis assemble it. 查看生成的字节码的另一种方法是dis它。 This actually shows the instructions that are performed when some code is executed: 这实际上显示了执行某些代码时执行的指令:

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

Which basically says the same. 基本上说的一样。 It loads the attribute __truediv__ of the constant 1.0 . 它加载常量1.0的属性__truediv__


Regarding your question 关于你的问题

And how can you use it in a more complex statement (if possible)? 如何在更复杂的陈述中使用它(如果可能的话)?

Even though it's possible you should never write code like that, simply because it's unclear what the code is doing. 尽管有可能你永远不应该编写这样的代码,只是因为它不清楚代码在做什么。 So please don't use it in more complex statements. 所以请不要在更复杂的陈述中使用它。 I would even go so far that you shouldn't use it in so "simple" statements, at least you should use parenthesis to separate the instructions: 我甚至会走得太远以至于你不应该在如此“简单”的陈述中使用它,至少你应该使用括号来分离指令:

f = (1.).__truediv__

this would be definetly more readable - but something along the lines of: 这肯定会更具可读性 - 但有些东西:

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

would be even better! 会更好!

The approach using partial also preserves python's data model (the 1..__truediv__ approach does not!) which can be demonstrated by this little snippet: 使用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

This is because 1. / (1+2j) is not evaluated by float.__truediv__ but with complex.__rtruediv__ - operator.truediv makes sure the reverse operation is called when the normal operation returns NotImplemented but you don't have these fallbacks when you operate on __truediv__ directly. 这是因为1. / (1+2j)不是由float.__truediv__评估的,而是complex.__rtruediv__ - operator.truediv确保在正常操作返回NotImplemented时调用反向操作但是当你没有这些后退时直接对__truediv__进行操作。 This loss of "expected behaviour" is the main reason why you (normally) shouldn't use magic methods directly. 这种“预期行为”的丧失是你(通常)不应该直接使用魔法的主要原因。

Two dots together may be a little awkward at first: 两个点在一起起初可能有点尴尬:

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

But it is the same as writing: 但它和写作一样:

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

Because float literals can be written in three forms: 因为float文字可以用三种形式编写:

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

What is f = 1..__truediv__ ? 什么是f = 1..__truediv__

f is a bound special method on a float with a value of one. f是一个值为1的float上的绑定特殊方法。 Specifically, 特别,

1.0 / x

in Python 3, invokes: 在Python 3中,调用:

(1.0).__truediv__(x)

Evidence: 证据:

class Float(float):
    def __truediv__(self, other):
        print('__truediv__ called')
        return super(Float, self).__truediv__(other)

and: 和:

>>> one = Float(1)
>>> one/2
__truediv__ called
0.5

If we do: 如果我们这样做:

f = one.__truediv__

We retain a name bound to that bound method 我们保留一个绑定到该绑定方法的名称

>>> f(2)
__truediv__ called
0.5
>>> f(3)
__truediv__ called
0.3333333333333333

If we were doing that dotted lookup in a tight loop, this could save a little time. 如果我们在紧密的循环中进行虚线查找,这可以节省一点时间。

Parsing the Abstract Syntax Tree (AST) 解析抽象语法树(AST)

We can see that parsing the AST for the expression tells us that we are getting the __truediv__ attribute on the floating point number, 1.0 : 我们可以看到解析表达式的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()))"

You could get the same resulting function from: 您可以从以下方式获得相同的结果函数:

f = float(1).__truediv__

Or 要么

f = (1.0).__truediv__

Deduction 扣除

We can also get there by deduction. 我们也可以通过演绎到达那里。

Let's build it up. 让我们建立它。

1 by itself is an int : 1本身就是一个int

>>> 1
1
>>> type(1)
<type 'int'>

1 with a period after it is a float: 1是浮动后的一段时间:

>>> 1.
1.0
>>> type(1.)
<type 'float'>

The next dot by itself would be a SyntaxError, but it begins a dotted lookup on the instance of the float: 下一个点本身就是一个SyntaxError,但它在float的实例上开始一个虚线查找:

>>> 1..__truediv__
<method-wrapper '__truediv__' of float object at 0x0D1C7BF0>

No one else has mentioned this - This is now a "bound method" on the float, 1.0 : 没有人提到这个 - 这是浮动的一个“绑定方法”1.0

>>> f = 1..__truediv__
>>> f
<method-wrapper '__truediv__' of float object at 0x127F3CD8>
>>> f(2)
0.5
>>> f(3)
0.33333333333333331

We could accomplish the same function much more readably: 我们可以更加可读地完成相同的功能:

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

Performance 性能

The downside of the divide_one_by function is that it requires another Python stack frame, making it somewhat slower than the bound method: 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]

Of course, if you can just use plain literals, that's even faster: 当然,如果你可以使用普通文字,那就更快了:

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