开发者

Returning a dict value for a key, or the key if the dict does not contain that key

Background: It had been a while since I had read https://stackoverflow.com/questions/228181/the-zen-of-python so I went to the interpreter and typed import this, then out of curiosity I inspected the contents of the newly imported this 开发者_Python百科module and saw that this has some interesting attributes, most notably:

>>> this.s
"Gur Mra bs Clguba, ol Gvz Crgref\n\nOrnhgvshy vf orggre guna htyl.\nRkcyvpvg vf orggre guna vzcyvpvg.\nFvzcyr vf orggre guna pbzcyrk.\nPbzcyrk vf orggre guna pbzcyvpngrq.\nSyng vf orggre guna arfgrq.\nFcnefr vf orggre guna qrafr.\nErnqnovyvgl pbhagf.\nFcrpvny pnfrf nera'g fcrpvny rabhtu gb oernx gur ehyrf.\nNygubhtu cenpgvpnyvgl orngf chevgl.\nReebef fubhyq arire cnff fvyragyl.\nHayrff rkcyvpvgyl fvyraprq.\nVa gur snpr bs nzovthvgl, ershfr gur grzcgngvba gb thrff.\nGurer fubhyq or bar-- naq cersrenoyl bayl bar --boivbhf jnl gb qb vg.\nNygubhtu gung jnl znl abg or boivbhf ng svefg hayrff lbh'er Qhgpu.\nAbj vf orggre guna arire.\nNygubhtu arire vf bsgra orggre guna *evtug* abj.\nVs gur vzcyrzragngvba vf uneq gb rkcynva, vg'f n onq vqrn.\nVs gur vzcyrzragngvba vf rnfl gb rkcynva, vg znl or n tbbq vqrn.\nAnzrfcnprf ner bar ubaxvat terng vqrn -- yrg'f qb zber bs gubfr!"
>>> this.d
{'A': 'N', 'C': 'P', 'B': 'O', 'E': 'R', 'D': 'Q', 'G': 'T', 'F': 'S', 'I': 'V', 'H': 'U', 'K': 'X', 'J': 'W', 'M': 'Z', 'L': 'Y', 'O': 'B', 'N': 'A', 'Q': 'D', 'P': 'C', 'S': 'F', 'R': 'E', 'U': 'H', 'T': 'G', 'W': 'J', 'V': 'I', 'Y': 'L', 'X': 'K', 'Z': 'M', 'a': 'n', 'c': 'p', 'b': 'o', 'e': 'r', 'd': 'q', 'g': 't', 'f': 's', 'i': 'v', 'h': 'u', 'k': 'x', 'j': 'w', 'm': 'z', 'l': 'y', 'o': 'b', 'n': 'a', 'q': 'd', 'p': 'c', 's': 'f', 'r': 'e', 'u': 'h', 't': 'g', 'w': 'j', 'v': 'i', 'y': 'l', 'x': 'k', 'z': 'm'}

I figured the Zen could be obtained from this.s by using the character mapping in this.d, so I came up with the following line that does in fact print the Zen of Python:

"".join(this.d[c] if c in this.d else c for c in this.s)

I then went back to Stack Overflow wiki linked above thinking that I would post this interesting information, but of course it was already there, and had been for 2 and a half years. However, that post uses the following method to obtain the Zen:

"".join(c in this.d and this.d[c] or c for c in this.s)

This saves a few characters, but it is slightly less obvious what is going on (in my opinion). So I figured I would check out which method this.py uses, of course it is a more elegant solution than either of the above:

"".join(this.d.get(c, c) for c in this.s)

Question: This process got me thinking, are there any performance differences between these three methods that make any of them clearly better?

Obviously the last one is great because it is way more readable, but I have heard there is some overhead associated with Python function calls so it wouldn't surprise me to learn that one of the other methods was faster.

So, if any of these methods are more efficient than the others, which is it and why?


Only way to know is to try :)

So here is my setup:

>>> import timeit
>>> cmd1 = """\
... "".join(this.d[c] if c in this.d else c for c in this.s)
... """
>>> cmd2 = """\
... "".join(c in this.d and this.d[c] or c for c in this.s)
... """
>>> cmd3 = """\
... "".join(this.d.get(c, c) for c in this.s)
... """
>>> cmd4 = """\
... _get=this.d.get;"".join(_get(c, c) for c in this.s)
... """
>>> t1 = timeit.Timer(cmd1, "import this")
>>> t2 = timeit.Timer(cmd2, "import this")
>>> t3 = timeit.Timer(cmd3, "import this")
>>> t4 = timeit.Timer(cmd4, "import this")

Results:

print "%.2f usec/pass" % (1000000 * t1.timeit(number=100000)/100000)
362.67 usec/pass

>>> print "%.2f usec/pass" % (1000000 * t2.timeit(number=100000)/100000)
364.25 usec/pass

>>> print "%.2f usec/pass" % (1000000 * t3.timeit(number=100000)/100000)
391.97 usec/pass

>>> print "%.2f usec/pass" % (1000000 * t4.timeit(number=100000)/100000)
246.91 usec/pass


As I expected (rather guessed),

_get=this.d.get; "".join(_get(c, c) for c in this.s)

proves to be the fastest. See Mike's answer for the timings.


Now, in this.d.get(c, c), get has to be evaluated every time it iterates.
But if you store the get function, it may save you some time.


Thanks Mike and N 1.1 for the answers, I was still curious about the relative performance of storing this.d locally before the generator, here is the setup:

>>> t1 = timeit.Timer('"".join(this.d[c] if c in this.d else c for c in this.s)', 'import this')
>>> t2 = timeit.Timer('"".join(c in this.d and this.d[c] or c for c in this.s)', 'import this')
>>> t3 = timeit.Timer('"".join(this.d.get(c, c) for c in this.s)', 'import this')
>>> # assigning local names for this.d
>>> t4 = timeit.Timer('d = this.d;"".join(d[c] if c in d else c for c in this.s)', 'import this')
>>> t5 = timeit.Timer('d = thid.d;"".join(c in d and d[c] or c for c in this.s)', 'import this')
>>> t6 = timeit.Timer('_get = this.d.get;"".join(_get(c, c) for c in this.s)', 'import this')

Results:

>>> print "%.2f usec/pass" % (1000000 * t1.timeit(number=10000)/10000)
404.46 usec/pass

>>> print "%.2f usec/pass" % (1000000 * t2.timeit(number=10000)/10000)
410.73 usec/pass

>>> print "%.2f usec/pass" % (1000000 * t3.timeit(number=10000)/10000)
432.35 usec/pass

>>> print "%.2f usec/pass" % (1000000 * t4.timeit(number=10000)/10000)
244.88 usec/pass

>>> print "%.2f usec/pass" % (1000000 * t5.timeit(number=10000)/10000)
258.15 usec/pass

>>> print "%.2f usec/pass" % (1000000 * t6.timeit(number=10000)/10000)
245.17 usec/pass

So it looks like the first and third are almost identical regarding performance once you get rid of the lookup overhead. These tests were with Python 2.5, not sure if that would make a difference.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜