What do backticks mean to the Python interpreter? Example: `num`
Asked Answered
M

3

98

I'm playing around with list comprehensions and I came across this little snippet on another site:

return ''.join([`num` for num in xrange(loop_count)])

I spent a few minutes trying to replicate the function (by typing) before realising the `num` bit was breaking it.

What does enclosing a statement in those characters do? From what I can see it is the equivalent of str(num). But when I timed it:

return ''.join([str(num) for num in xrange(10000000)])

It takes 4.09 seconds whereas:

return ''.join([`num` for num in xrange(10000000)])

takes 2.43 seconds.

Both give identical results, but one is a lot slower. What is going on here?

Oddly... repr() gives slightly slower results than `num`. 2.99 seconds vs 2.43 seconds. I am using Python 2.6 (haven't tried 3.0 yet).

Missing answered 4/11, 2009 at 11:0 Comment(1)
After reading the "another site" at skymind.com/~ocrow/python_string, I had a similar question and found this page. Nice question and nice answer :)Cheryllches
S
137

Backticks are a deprecated alias for repr(). Don't use them any more; the syntax was removed in Python 3.0.

Using backticks seems to be faster than using repr(num) or num.__repr__() in version 2.x. I guess it's because additional dictionary lookup is required in the global namespace (for repr), or in the object's namespace (for __repr__), respectively.


Using the dis module proves my assumption:

def f1(a):
    return repr(a)

def f2(a):
    return a.__repr__()

def f3(a):
    return `a`

Disassembling shows:

>>> import dis
>>> dis.dis(f1)
  3           0 LOAD_GLOBAL              0 (repr)
              3 LOAD_FAST                0 (a)
              6 CALL_FUNCTION            1
              9 RETURN_VALUE
>>> dis.dis(f2)
  6           0 LOAD_FAST                0 (a)
              3 LOAD_ATTR                0 (__repr__)
              6 CALL_FUNCTION            0
              9 RETURN_VALUE
>>> dis.dis(f3)
  9           0 LOAD_FAST                0 (a)
              3 UNARY_CONVERT
              4 RETURN_VALUE

f1 involves a global lookup for repr, f2 an attribute lookup for __repr__, whereas the backtick operator is implemented in a separate opcode. Since there is no overhead for dictionary lookup (LOAD_GLOBAL/LOAD_ATTR) nor for function calls (CALL_FUNCTION), backticks are faster.

I guess that the Python folks decided that having a separate low-level operation for repr() is not worth it, and having both repr() and backticks violates the principle

"There should be one-- and preferably only one --obvious way to do it"

so the feature was removed in Python 3.0.

Siqueiros answered 4/11, 2009 at 11:3 Comment(7)
I wanted to find, how you can replace backticks with some function call, but it seems that it is not possible, or is it?Indomitability
Use repr() instead of backticks. Backticks are depreciated syntax for repr() come 3.0. I actually prefer the look of backticks rather then calling ANOTHER function.Missing
The reason backticks are deprecated is also because of the ` character itself; it can be hard to type (on some keyboards), hard to see what it is, hard to print correctly in Python books. Etc.Venetian
@kaizer.se: Thanks for pointing that out. This is probably the main reason for dropping backticks, see Guidos statement in the mailing list archives: mail.python.org/pipermail/python-ideas/2007-January/000054.htmlSiqueiros
The original question this was posted was because I couldn't actually find backticks on my keyboard ;) Below the tilde it seems after googling.Missing
Why there is no overhead for dictionary lookup nor for function calls is true?Agglomeration
@Stallman see the output of dis, it shows how Python implements the functions internally. LOAD_GLOBAL means there is a dictionary lookup at the global level for the function repr, that is called afterwards. LOAD_ATTR is a dictionary lookup for the attribute __repr__ that also is called. Both is not needed for the backticks version, where there is a special opcode UNARY_CONVERT without LOAD_* and CALL_FUNCTION. So the code that does what repr() does is reached more directly, and the overall function performs better.Siqueiros
S
10

Backtick quoting is generally non-useful and is gone in Python 3.

For what it's worth, this:

''.join(map(repr, xrange(10000000)))

is marginally faster than the backtick version for me. But worrying about this is probably a premature optimisation.

Sallyanne answered 4/11, 2009 at 11:32 Comment(9)
Why go a step backwards and use map instead of list/iterator comprehensions?Govea
Actually, timeit yields faster results for ''.join(map(repr, xrange(0, 1000000))) than for ''.join([repr(i) for i in xrange(0, 1000000)]) (even worse for ''.join( (repr(i) for i in xrange(0, 1000000)) )). It's a bit disappointing ;-)Dormer
bobince's result is not surprising to me. As as rule of thumb, implicit loops in Python are faster than explicit ones, often dramatically faster. map is implemented in C, using a C loop, that is much faster than a Python loop executed in the virtual machine.Siqueiros
Not surprised either, it's just too bad for the list comprehensions' reputation (with a 30% hit in this example). But I'd rather have clear than blazing-speed code unless this is really important, so no big deal here. That being said, the map() function doesn't strike me as unclear, LC are sometimes overrated.Dormer
map seems perfectly clear and concise to me, and I don't even know Python.Globe
@Globe +1 - I teach Python, and still think map is perfectly clear, though I'd use a comprehension in practice, because this is not worth jarring other developers over. Some things are worth the friction; this isn't.Grapeshot
@CarlSmith If it weren't being evaluated immediately, then I'd use a comprehension; but it just seems like pointless bloat when all the values need to be used immediately in this particular case. That being said, there are lots of strong use cases for list comprehensions; this just isn't one of them.Globe
@Globe Good point. This code specifically does kind of beg the use of map.Grapeshot
@Dormer this isn't implicit/explicit loops, this is because in the map case, there's only one global dict lookup for "repr", while in the list comprehension case, global lookup times dominate the speed.Viminal
E
1

My guess is that num doesn't define the method __str__(), so str() has to do a second lookup for __repr__.

The backticks look directly for __repr__. If that's true, then using repr() instead of the backticks should give you the same results.

Eccrine answered 4/11, 2009 at 11:5 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.