<p>除非代码是程序中最热门的代码,否则实际上不建议这样做,但有一些方法可以改进:</p>
<pre><code>def covert_to_boolean(a)
return [bool(x) for x in a]
# Or the straightforward way of converting back to 1/0
return [int(bool(x)) for x in a]
</code></pre>
<p>首先,如果<code>a</code>足够大,因为<code>int</code>/<code>bool</code>是用C实现的内置程序,您可以使用<code>map</code>删除字节码解释器开销:</p>
<pre><code>def covert_to_boolean(a)
return [*map(bool, a)]
# Or converting back to 1/0
return [*map(int, map(bool, a))]
</code></pre>
<p>另一个节省可以来自不使用<code>bool</code>构造函数(C构造函数调用在CPython上有不可避免的开销,即使结果实际上没有“构造”任何东西),并且用<code>operator.truth</code>(一个只接受一个参数的普通函数,CPython对其进行了大量优化)替换它可以显著减少开销,而且使用它可以将开销减少40%:</p>
<pre><code>>>> import random
>>> from operator import truth
>>> a = random.choices([*[0] * 100, *range(1, 101)], k=1000)
>>> %%timeit -r5
... [bool(x) for x in a]
...
...
248 µs ± 7.82 µs per loop (mean ± std. dev. of 5 runs, 1000 loops each)
>>> %%timeit -r5
... [*map(bool, a)]
...
...
140 µs ± 2.5 µs per loop (mean ± std. dev. of 5 runs, 10000 loops each)
>>> %%timeit -r5
... [*map(truth, a)]
...
...
81.3 µs ± 3.91 µs per loop (mean ± std. dev. of 5 runs, 10000 loops each)
</code></pre>
<p><code>map(bool</code>提高了大约45%的单子理解力,反过来又被<code>map(truth</code>击败了40%(<code>map(truth</code>几乎占据了单子理解力的三分之一)。你知道吗</p>
<p>如果结果必须是<code>int</code>,我们可以将其扩展为<code>[*map(int, map(truth, a))]</code>,但是<code>int</code>是一个构造函数,即使它返回单实例值(CPython缓存-5到256的单个副本作为实现细节),它仍然会支付构造函数开销(更糟的是,因为它可以接受关键字参数)。没有像<code>bool</code>那样的等效“convert to true<code>int</code>”函数有<code>operator.truth</code>,但是您可以通过“adding to <code>0</code>”将您的方式欺骗为一个:</p>
<pre><code>>>> %%timeit -r5
... [int(bool(x)) for x in a]
...
...
585 µs ± 65.2 µs per loop (mean ± std. dev. of 5 runs, 1000 loops each)
>>> %%timeit -r5
... [*map(int, map(bool, a))]
...
...
363 µs ± 58.6 µs per loop (mean ± std. dev. of 5 runs, 1000 loops each)
>>> %%timeit -r5
... [*map((0).__add__, map(truth, a))]
...
...
168 µs ± 2.2 µs per loop (mean ± std. dev. of 5 runs, 10000 loops each)
</code></pre>
<p><code>(0).__add__</code>只是利用了这样一个事实:将<code>bool</code>添加到<code>0</code>会产生<code>0</code>或<code>1</code>,<code>__add__</code>的开销远远低于构造函数;在这种情况下,从列表理解切换到<code>map</code>(甚至嵌套的<code>map</code>)节省了近40%,从<code>int</code>/<code>bool</code>切换到<code>(0).__add__</code>/<code>truth</code>节省了几乎55%的剩余时间,运行时间减少了70%以上。你知道吗</p>
<p>再说一次,要清楚,<strong>不要这样做,除非:</p>
<ol>
<li>您已经分析了,转换实际上是您的代码中的关键路径,在速度方面,以及</li>
<li>输入不是太小(如果<code>a</code>只是五个元素,那么调用<code>map</code>的设置开销将超过避免每个循环使用字节码所节省的微小开销)</li>
</ol>
<p>但当它出现的时候,很高兴知道。<code>bool</code>是Python中速度最慢的东西之一间接费用:生产性工作类似于<code>int</code>的事物的<code>int</code>比率也同样糟糕。你知道吗</p>
<p>不过,还有最后一件事要检查。也许把事情推进语法,避免函数调用,可以节省更多。碰巧,答案是“是的,对他们中的一个来说”:</p>
<pre><code>>>> %%timeit -r5
... [not not x for x in a] # Worse than map
...
...
122 µs ± 6.6 µs per loop (mean ± std. dev. of 5 runs, 10000 loops each)
>>> %%timeit -r5
... [0 + (not not x) for x in a] # BETTER than map!!!
...
...
158 µs ± 22.4 µs per loop (mean ± std. dev. of 5 runs, 10000 loops each)
>>> %%timeit -r5
...: [0 + x for x in map(truth, a)] # Somehow not the best of both worlds...
...:
...:
177 µs ± 5.77 µs per loop (mean ± std. dev. of 5 runs, 10000 loops each)
</code></pre>
<p>虽然<code>[not not x for x in a]</code>输给了<code>[*map(truth, a)]</code>,<code>[0 + (not not x) for x in a]</code>实际上打败了<code>[*map((0).__add__, map(truth, a))]</code>(碰巧,通过<code>tp_add</code>槽周围的包装器调用<code>(0).__add__</code>会有一些开销,这可以通过在Python层实际使用<code>+</code>来避免)。不过,将每种解决方案中的最佳方案(<code>map(truth</code>与list comp中的<code>0 +</code>)混合使用实际上并没有给我们带来好处(读取字节码开销大致是一个固定成本,<code>not not</code>甚至比<code>operator.truth</code>还要快)。关键是,除非您真的需要,否则这些都不值得,而且性能可能是不直观的。我曾经有过需要它的代码,所以你可以从我的测试中受益。你知道吗</p>