最有效的方法是什么时候 else 被做的最多,来创建 if-elif-elif-else 语句?

我有一个 in if-elif-elif-else 语句,其中99% 的时间都执行 else 语句:

if something == 'this':
doThis()
elif something == 'that':
doThat()
elif something == 'there':
doThere()
else:
doThisMostOfTheTime()

这个构造是完成 很多的,但是因为它在遇到 else 之前遍历了所有条件,所以我感觉这不是很有效,更不用说 Python 了。另一方面,它确实需要知道是否满足这些条件中的任何一个,所以无论如何它都应该对其进行测试。

有没有人知道是否以及如何能够更有效地做到这一点,或者这仅仅是最好的可能的方式来做到这一点?

90834 次浏览

我会编一本字典:

options = {'this': doThis,'that' :doThat, 'there':doThere}

现在只用:

options.get(something, doThisMostOfTheTime)()

如果在 options结构中没有找到 something,那么 dict.get将返回默认值 doThisMostOfTheTime

一些时间比较:

剧本:

from random import shuffle
def doThis():pass
def doThat():pass
def doThere():pass
def doSomethingElse():pass
options = {'this':doThis, 'that':doThat, 'there':doThere}
lis = range(10**4) + options.keys()*100
shuffle(lis)


def get():
for x in lis:
options.get(x, doSomethingElse)()


def key_in_dic():
for x in lis:
if x in options:
options[x]()
else:
doSomethingElse()


def if_else():
for x in lis:
if x == 'this':
doThis()
elif x == 'that':
doThat()
elif x == 'there':
doThere()
else:
doSomethingElse()

结果:

>>> from so import *
>>> %timeit get()
100 loops, best of 3: 5.06 ms per loop
>>> %timeit key_in_dic()
100 loops, best of 3: 3.55 ms per loop
>>> %timeit if_else()
100 loops, best of 3: 6.42 ms per loop

对于 10**5不存在的密钥和100个有效密钥: :

>>> %timeit get()
10 loops, best of 3: 84.4 ms per loop
>>> %timeit key_in_dic()
10 loops, best of 3: 50.4 ms per loop
>>> %timeit if_else()
10 loops, best of 3: 104 ms per loop

因此,对于一个普通的字典来说,使用 key in options检查密钥是最有效的方法:

if key in options:
options[key]()
else:
doSomethingElse()

密码..。

options.get(something, doThisMostOfTheTime)()

... 看起来应该更快,但实际上比 if... elif... else结构要慢,因为它必须调用一个函数,这在紧凑的循环中会造成很大的性能开销。

想想这些例子..。

1.py

something = 'something'


for i in xrange(1000000):
if something == 'this':
the_thing = 1
elif something == 'that':
the_thing = 2
elif something == 'there':
the_thing = 3
else:
the_thing = 4

2.py

something = 'something'
options = {'this': 1, 'that': 2, 'there': 3}


for i in xrange(1000000):
the_thing = options.get(something, 4)

3.py

something = 'something'
options = {'this': 1, 'that': 2, 'there': 3}


for i in xrange(1000000):
if something in options:
the_thing = options[something]
else:
the_thing = 4

4.py

from collections import defaultdict


something = 'something'
options = defaultdict(lambda: 4, {'this': 1, 'that': 2, 'there': 3})


for i in xrange(1000000):
the_thing = options[something]

注意他们使用的 CPU 时间。

1.py: 160ms
2.py: 170ms
3.py: 110ms
4.py: 100ms

使用 time(1)的用户时间。

选项 # 4确实有额外的内存开销,即为每个明显的键丢失添加一个新项目,因此如果您预期明显的键丢失的数量是无限的,我会选择选项 # 3,这仍然是对原始结构的重大改进。

Are you able to use pypy?

保持你的原始代码,但运行它在 Pypy 给我50倍的速度。

CPython:

matt$ python
Python 2.6.8 (unknown, Nov 26 2012, 10:25:03)
[GCC 4.2.1 Compatible Apple Clang 3.0 (tags/Apple/clang-211.12)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>>
>>> from timeit import timeit
>>> timeit("""
... if something == 'this': pass
... elif something == 'that': pass
... elif something == 'there': pass
... else: pass
... """, "something='foo'", number=10000000)
1.728302001953125

派比:

matt$ pypy
Python 2.7.3 (daf4a1b651e0, Dec 07 2012, 23:00:16)
[PyPy 2.0.0-beta1 with GCC 4.2.1] on darwin
Type "help", "copyright", "credits" or "license" for more information.
And now for something completely different: ``a 10th of forever is 1h45''
>>>>
>>>> from timeit import timeit
>>>> timeit("""
.... if something == 'this': pass
.... elif something == 'that': pass
.... elif something == 'there': pass
.... else: pass
.... """, "something='foo'", number=10000000)
0.03306388854980469

这里有一个例子,它将具有动态条件的 if 转换为字典。

selector = {lambda d: datetime(2014, 12, 31) >= d : 'before2015',
lambda d: datetime(2015, 1, 1) <= d < datetime(2016, 1, 1): 'year2015',
lambda d: datetime(2016, 1, 1) <= d < datetime(2016, 12, 31): 'year2016'}


def select_by_date(date, selector=selector):
selected = [selector[x] for x in selector if x(date)] or ['after2016']
return selected[0]

这是一种方法,但可能不是最简洁的方法,因为对于不熟悉 Python 的人来说,这种方法的可读性较差。

人们出于安全原因对 exec发出警告,但这是一个理想的情况。
这是一个简单的状态机。

Codes = {}
Codes [0] = compile('blah blah 0; nextcode = 1')
Codes [1] = compile('blah blah 1; nextcode = 2')
Codes [2] = compile('blah blah 2; nextcode = 0')


nextcode = 0
While True:
exec(Codes[nextcode])

最近我发现了一种替代“嵌套 if else”的方法,它可以将函数的运行时间从2.5小时减少到2分钟。.砰!我们开始吧:

早期代码
Bin = lambda x: “ Unknown”if x = = 0 else (“ High”if x > 75 else (“ Medium”if x > 50 and x < = 75 else (“ Medium _ Low”if x > 25 and x < = 50 else“ Low”)))

应用(箱) 时间2.5小时

Optimize Code

定义 Dictionary 的另一种方法来嵌套 if else
 def dict_function(*args):
'Pass in a list of tuples, which will be key/value pairs'
ret = {}
for k,v in args:
for i in k:
ret[i] = v
return ret
Dict = dict_function(([0],"Unknown"),(range(1,25),"Low"),(range(25,50),"Medium_Low"),(range(50,75),"Medium"),(range(75,100),"High"))


col.apply(lambda x:Dict[x])

在给定范围内使用 dict- 函数生成多个键值对

我最近也遇到了同样的问题,虽然不是关于性能,但是我不喜欢创建函数并手动将它们添加到 dict 中的“ API”。我想要一个类似于 functools.singledispatch的 API,但是基于值而不是基于类型进行分派。那么..。

def value_dispatch(func):
"""value-dispatch function decorator.
Transforms a function into a function, that dispatches its calls based on the
value of the first argument.
"""
funcname = getattr(func, '__name__')
registry = {}


def dispatch(arg):
"""return the function that matches the argument"""
return registry.get(arg, func)


def register(arg):
def wrapper(func):
"""register a function"""
registry[arg] = func
return func
return wrapper


def wrapper(*args, **kwargs):
if not args:
raise ValueError(f'{funcname} requires at least 1 positional argument')
return dispatch(args[0])(*args, **kwargs)


wrapper.register = register
wrapper.dispatch = dispatch
wrapper.registry = registry
return wrapper

使用方法如下:

@value_dispatch
def handle_something():
print("default")


@handle_something.register(1)
def handle_one():
print("one")


handle_something(1)
handle_something(2)

PS: 我创建了 a snippet on Gitlab作为参考

我尝试使用 python 3.10中引入的 match 语句:

5.py

something = 'something'
for i in range(10000000):
match something:
case "this":
the_thing = 1
case "that":
the_thing = 2
case "there":
the_thing = 3
case _:
the_thing = 4

下面是我在3.10.0中得到的结果:
1.py: 1.4 s
2.py: 0.9 s
3.py: 0.7s
4.py: 0.7 s
5.py: 1.0 s
I thought I would get something similar to 1.py but it is quite faster.

你可以用 switch-case 类型模仿 if-elif-else,比如使用 dictionary 和 lambda 函数

For example:

x = 5
y = 5
operator = 'add'


def operation(operator, x, y):
return {
'add': lambda: x+y,
'sub': lambda: x-y,
'mul': lambda: x*y,
'div': lambda: x/y
}.get(operator, lambda: None)()


result = operation(operator, x, y)
print(result)