Jinglesting
Jinglesting

Reputation: 517

Fastest way to make a modified copy of an immutable object in python

If I have a tuple object which stores some immutable objects (like ints), and I need to create a modified version of this tuple as efficiently/fast as possible, what is the best way to do this?

Here is a simplified example of what I currently have.

orig_tuple = (1, 0, 0)
new_tuple = (some_function(element) for element in orig_tuple)

Is this as fast as it gets? Does the list comprehension add much overhead?

Upvotes: 1

Views: 502

Answers (2)

Axel Persinger
Axel Persinger

Reputation: 331

To benchmark or profile your ideas, try using dis and timeit libraries that are built into Python. I'm using Python 2.7 on Windows 7 x64.

import copy
import dis
import timeit

def method_one():
    def add_one(i):
        return i+1

    orig_tuple = (1, 0, 0)
    new_tuple = (add_one(element) for element in orig_tuple)

def method_two():
    def add_one(i):
        return i+1

    orig_tuple = (1, 0, 0)
    new_tuple = copy.deepcopy(orig_tuple)
    for i in new_tuple:
        i = add_one(i)

print dis.dis(method_one)
print timeit.timeit(method_one, number=10000)

print dis.dis(method_two)
print timeit.timeit(method_two, number=10000)

Produced:

D:\Users\user>python help.py
  6           0 LOAD_CONST               1 (<code object add_one at 01DA6B60, file "help.py", line 6
>)
              3 MAKE_FUNCTION            0
              6 STORE_DEREF              0 (add_one)

  9           9 LOAD_CONST               5 ((1, 0, 0))
             12 STORE_FAST               0 (orig_tuple)

 10          15 LOAD_CLOSURE             0 (add_one)
             18 BUILD_TUPLE              1
             21 LOAD_CONST               4 (<code object <genexpr> at 01DA6CC8, file "help.py", line
 10>)
             24 MAKE_CLOSURE             0
             27 LOAD_FAST                0 (orig_tuple)
             30 GET_ITER
             31 CALL_FUNCTION            1
             34 STORE_FAST               1 (new_tuple)
             37 LOAD_CONST               0 (None)
             40 RETURN_VALUE
None
0.0088386
 13           0 LOAD_CONST               1 (<code object add_one at 020C6F50, file "help.py", line 1
3>)
              3 MAKE_FUNCTION            0
              6 STORE_FAST               0 (add_one)

 16           9 LOAD_CONST               4 ((1, 0, 0))
             12 STORE_FAST               1 (orig_tuple)

 17          15 LOAD_GLOBAL              0 (copy)
             18 LOAD_ATTR                1 (deepcopy)
             21 LOAD_FAST                1 (orig_tuple)
             24 CALL_FUNCTION            1
             27 STORE_FAST               2 (new_tuple)

 18          30 SETUP_LOOP              26 (to 59)
             33 LOAD_FAST                2 (new_tuple)
             36 GET_ITER
        >>   37 FOR_ITER                18 (to 58)
             40 STORE_FAST               3 (i)

 19          43 LOAD_FAST                0 (add_one)
             46 LOAD_FAST                3 (i)
             49 CALL_FUNCTION            1
             52 STORE_FAST               3 (i)
             55 JUMP_ABSOLUTE           37
        >>   58 POP_BLOCK
        >>   59 LOAD_CONST               0 (None)
             62 RETURN_VALUE
None
0.1026118

As you can see, it looks like creating that generator is much faster. method_two was the only other way I could think of off the top of my head to do what you wanted to accomplish. If you have any other ideas, test them and edit your question if you need feedback.

Upvotes: 0

lpozo
lpozo

Reputation: 598

Function calls is what adds overhead, not listcomp. And this returns a generator, not a tuple, be careful it is not the same.

Upvotes: 1

Related Questions