一种有效的Pythonic映射值的方法?

时间:2013-10-18 20:03:42

标签: python

我有一些值要重新映射,因为有两种方法可以指定规则。简单的if / else方法似乎是更有效的变体,但我想知道是否存在同样有效但更加pythonic的方法?

 if mod == "I": mod = "+"
 elif mod == "E": mod = "-"
 elif mod == "D": mod = ":"
 elif mod == "M": mod = "."

不那么有效的映射方法:

 mod = { "I":"+", "E":"-", "D":":", "M":"." }.get(mod, mod)

5 个答案:

答案 0 :(得分:2)

映射将导致O(1)查找。条件将导致O(N)。当然,如果你想得到所有关于它的挑剔,你的地图实现中还有额外的函数调用。

而不是理论化,I ran a quick benchmark。结果(删除get调用并严格使用数组访问器,结果:

   2 function calls in 0.025 seconds

   Ordered by: standard name

   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
        1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects}
        1    0.025    0.025    0.025    0.025 {range}


   4 function calls in 0.035 seconds

   Ordered by: standard name

   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
        2    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects}
        2    0.035    0.018    0.035    0.018 {range}

现在可以肯定的是,这只使用了给定的案例。我的假设是,如果条件的运行时间将随着案例的数量线性增加,而地图查找将保持不变。

答案 1 :(得分:1)

如果你有固定的少量案例,if-elif-else方法是最快的(我不明白为什么它会是非Pythonic);对于足够多的案例,dict查找会更好。

如果案例集是动态的,dict方法当然是唯一可行的方法。

另外,第三种非正统的方法是使用宏观元编程。这对于vanilla python是不可能的,但是有一些库,例如https://github.com/lihaoyi/macropy允许您以(可以说是)干净的方式执行此操作(很可能未经Python社区或Guido批准)。

P.S。第二个想法,宏观方法可能不适用于Python,就像在Python的大多数宏实现的情况下一样,它试图坚持使用vanilla Python的语法;即,从宏内生成if-elif-else块是不可能的。

答案 2 :(得分:1)

请记住,多级if语句会生成大量字节代码,主要在Python级别进行评估,而字典访问则在解释器的优化C代码中进行。此外,if语句需要平均n/2次比较,如果有n种可能性:它必须按顺序检查每种可能性。

故事的寓意:dicts可能足够快,但如果有疑问,可以找到你的真正的瓶颈,而不是你怀疑的那个瓶颈。


比较

def f(mod):
    if mod == "I": return "+"
    elif mod == "E": return "-"
    elif mod == "D": return ":"
    elif mod == "M": return "."

dis.dis(f)
  4           0 LOAD_FAST                0 (mod)
              3 LOAD_CONST               1 ('I')
              6 COMPARE_OP               2 (==)
              9 POP_JUMP_IF_FALSE       16
             12 LOAD_CONST               2 ('+')
             15 RETURN_VALUE

  5     >>   16 LOAD_FAST                0 (mod)
             19 LOAD_CONST               3 ('E')
             22 COMPARE_OP               2 (==)
             25 POP_JUMP_IF_FALSE       32
             28 LOAD_CONST               4 ('-')
             31 RETURN_VALUE

  6     >>   32 LOAD_FAST                0 (mod)
             35 LOAD_CONST               5 ('D')
             38 COMPARE_OP               2 (==)
             41 POP_JUMP_IF_FALSE       48
             44 LOAD_CONST               6 (':')
             47 RETURN_VALUE

  7     >>   48 LOAD_FAST                0 (mod)
             51 LOAD_CONST               7 ('M')
             54 COMPARE_OP               2 (==)
             57 POP_JUMP_IF_FALSE       64
             60 LOAD_CONST               8 ('.')
             63 RETURN_VALUE
        >>   64 LOAD_CONST               0 (None)
             67 RETURN_VALUE


d={"I": "+", "E": "-", "D": ":", "M": "."}
def g(mod):
    return d.get(mod, mod)

12           0 LOAD_GLOBAL              0 (d)
             3 LOAD_ATTR                1 (get)
             6 LOAD_FAST                0 (mod)
             9 LOAD_FAST                0 (mod)
            12 CALL_FUNCTION            2
            15 RETURN_VALUE

答案 3 :(得分:0)

字符串方法具有可用的转换功能。您必须构建一个长度为256个字符的转换表。这是我使用的代码片段:

translationTable = ' '*256
translationTable = translationTable[:68]+':'+translationTable[69:] # D to :
translationTable = translationTable[:69]+'-'+translationTable[70:] # E to -
translationTable = translationTable[:73]+'+'+translationTable[74:] # I to +
translationTable = translationTable[:77]+'.'+translationTable[78:] # M to .
print 'EIDM'.translate(translationTable)

输出:

-+:.

答案 4 :(得分:0)

这不是一个真正的答案,但很多评论集中在表现上,因为我问过。所以我到目前为止对答案进行了一些性能测试:

from datetime import datetime
from string import maketrans
tr_table = maketrans('IEDM', '+-:.')
dictionary = { "I":"+", "E":"-", "D":":", "M":"." }
if_else_val = "E"

N_OPS = 100000

now = datetime.now

def time(func):
    s = now()
    func()
    print "%s took %d ms for %d operations" % (func.__name__, (now() - s).microseconds, N_OPS)

def translation_table():
    for i in xrange(N_OPS):
        "I".translate(tr_table)
        "E".translate(tr_table)
        "D".translate(tr_table)
        "M".translate(tr_table)

def dict_lookup():
    for i in xrange(N_OPS):
        dictionary.get("I")
        dictionary.get("E")
        dictionary.get("D")
        dictionary.get("M")

def if_else():
    for i in xrange(N_OPS):
        if if_else_val == "I": pass
        elif if_else_val == "E": pass
        elif if_else_val == "D": pass
        elif if_else_val == "M": pass

time(if_else)
time(translation_table)
time(dict_lookup)

结果如下:

if_else took 12474 ms for 100000 operations
translation_table took 81650 ms for 100000 operations
dict_lookup took 66385 ms for 100000 operations