我怎样才能忽略
ZeroDivisionError
并做出n / 0 == 0
?
除法前检查分母是否为零。这避免了捕获异常的开销,如果您希望大量除以零,这可能会更有效。
def weird_division(n, d):
return n / d if d else 0
当您想要高效处理
ZeroDivisionError
(除以零)时,您不应该使用异常或条件。
result = b and a / b or 0 # a / b
b != 0
时,我们有True and a / b or 0
。 True and a / b
等于 a / b
。 a / b or 0
等于 a / b
。b == 0
时,我们有False and a / b or 0
。 False and a / b
等于 False
。 False or 0
等于 0
。Timer unit: 1e-06 s
Total time: 118.362 s
File: benchmark.py
Function: exception_div at line 3
Line # Hits Time Per Hit % Time Line Contents
==============================================================
3 @profile
4 def exception_div(a, b):
5 100000000 23419098.5 0.2 19.8 try:
6 100000000 40715642.9 0.4 34.4 return a / b
7 100000000 28910860.8 0.3 24.4 except ZeroDivisionError:
8 100000000 25316209.7 0.3 21.4 return 0
Total time: 23.638 s
File: benchmark.py
Function: conditional_div at line 10
Line # Hits Time Per Hit % Time Line Contents
==============================================================
10 @profile
11 def conditional_div(a, b):
12 100000000 23638033.3 0.2 100.0 return a / b if b else 0
Total time: 23.2162 s
File: benchmark.py
Function: logic_div at line 14
Line # Hits Time Per Hit % Time Line Contents
==============================================================
14 @profile
15 def logic_div(a, b):
16 100000000 23216226.0 0.2 100.0 return b and a / b or 0
您可以使用
try
/except
块来实现此目的。
def foo(x,y):
try:
return x/y
except ZeroDivisionError:
return 0
>>> foo(5,0)
0
>>> foo(6,2)
3.0
我认为
try
except
(如 Cyber 的回答)通常是最好的方法(而且更Pythonic:请求宽恕比请求许可更好!),但这是另一个:
def safe_div(x,y):
if y == 0:
return 0
return x / y
支持这样做的一个论点是,如果你期望
ZeroDivisionError
经常发生,那么提前检查 0 分母会快很多(这是 python 3):
import time
def timing(func):
def wrap(f):
time1 = time.time()
ret = func(f)
time2 = time.time()
print('%s function took %0.3f ms' % (f.__name__, int((time2-time1)*1000.0)))
return ret
return wrap
def safe_div(x,y):
if y==0: return 0
return x/y
def try_div(x,y):
try: return x/y
except ZeroDivisionError: return 0
@timing
def test_many_errors(f):
print("Results for lots of caught errors:")
for i in range(1000000):
f(i,0)
@timing
def test_few_errors(f):
print("Results for no caught errors:")
for i in range(1000000):
f(i,1)
test_many_errors(safe_div)
test_many_errors(try_div)
test_few_errors(safe_div)
test_few_errors(try_div)
输出:
Results for lots of caught errors:
safe_div function took 185.000 ms
Results for lots of caught errors:
try_div function took 727.000 ms
Results for no caught errors:
safe_div function took 223.000 ms
Results for no caught errors:
try_div function took 205.000 ms
因此,对于很多(或实际上是全部)错误,使用
try
except
的速度会慢 3 到 4 倍;也就是说:对于捕获错误的迭代来说,速度要慢 3 到 4 倍。当错误很少(或者实际上没有)时,使用 if
语句的版本会稍微慢一些(10% 左右)。 (if Y Z=X/Y else Z=0)
if y !=0 :
z = x/y
else:
z = 0
或者你可以使用:
z = ( x / y ) if y != 0 else 0
如果您尝试除以两个整数列表,您可以使用:
z = [j/k if k else 0 for j, k in zip(x, y)]
这里,x 和 y 是两个整数列表。
def safe_division(numerator, denominator):
"""Return 0 if denominator is 0."""
return denominator and numerator / denominator
返回零
denominator
或除法结果。字节码:
In [51]: dis(safe_division)
25 0 LOAD_FAST 1 (denominator)
2 JUMP_IF_FALSE_OR_POP 5 (to 10)
4 LOAD_FAST 0 (numerator)
6 LOAD_FAST 1 (denominator)
8 BINARY_TRUE_DIVIDE
>> 10 RETURN_VALUE
返回零
denominator
无需使用
LOAD_CONST
加载零。
如果您不介意卷起袖子来获得更多性能,这里有一个 C++ 版本:static inline double div_positive_or_0(double a, double b) noexcept {
// Convert the quotient of division by non-positive numbers to 0.
// vpandpd turns quotient to 0 without branches.
// Using Intel intrinsincs to convert double to __m128d involves extra register copies.
// Use inline assembly to avoid unnecessary register copying.
a /= b;
asm("vcmpltsd %[b], %[z], %[b] \n\t"
"vandpd %[a], %[a], %[b]"
:[a]"+v"(a),[b]"+v"(b)
:[z]"v"(_mm_setzero_pd())
:);
return a;
}
x=0,y=0
print (y/(x or not x))
输出:
>>>x=0
>>>y=0
>>>print(y/(x or not x))
0.0
>>>x =1000
>>>print(y/(x or not x))
0.000999000999000999
如果 x 不等于 0,not x 将为 false,因此此时它会除以实际的 x。
的解决方案会更快。如果感觉 conditional_div
通常应该因其自然语言可读性而被首选,但如果我能准确理解为什么
logic_div
更快,这可能会在将来对我有所帮助。我为此寻找了 python 的dis
。>>> conditional_div = lambda n,d: n/d if d else 0
>>> logic_div = lambda n,d: d and n/d or 0
>>> dis.dis(conditional_div)
1 0 LOAD_FAST 1 (d)
2 POP_JUMP_IF_FALSE 12
4 LOAD_FAST 0 (n)
6 LOAD_FAST 1 (d)
8 BINARY_TRUE_DIVIDE
10 RETURN_VALUE
>> 12 LOAD_CONST 1 (0)
14 RETURN_VALUE
>>> dis.dis(logic_div)
1 0 LOAD_FAST 1 (d)
2 POP_JUMP_IF_FALSE 12
4 LOAD_FAST 0 (n)
6 LOAD_FAST 1 (d)
8 BINARY_TRUE_DIVIDE
10 JUMP_IF_TRUE_OR_POP 14
>> 12 LOAD_CONST 1 (0)
>> 14 RETURN_VALUE
而且看起来
logic_div
实际上应该有一个额外的步骤。直到“8”,两个字节码都是相同的。在“10”时,
conditional_div
只会返回一个值,而logic_div
必须在为真时进行跳转,然后返回。也许替代方案 ..._OR_POP
比返回更快,所以在一定程度上它的最后一步会更短?但激活 ..._OR_POP
的唯一方法是分子为零且分母非零。当分母为零时,两个字节码采用相同的路线。这感觉不是一个令人满意的结论。如果我误解了什么,也许有人可以解释一下。供参考
>>> sys.version
'3.9.6 (tags/v3.9.6:db3ff76, Jun 28 2021, 15:26:21) [MSC v.1929 64 bit (AMD64)]'