1

I'm wondering if there's a reason to use deepcopy instead of this:
list1 = [[1,2],[3,4],[5,6]]
list2 = eval(str(list1))
If there's another cool way of doing this I would love to know.

Ivan Sycz
  • 13
  • 2
  • 1
    `deepcody()` should be faster - it doesn't need to convert to string and later parse it back to list. Simply `eval(str())` is only waste of time and very strange method to duplicate object. – furas Dec 07 '20 at 13:28
  • If the user controls the content of list1, then you could probably get an arbitrary code execution vulnerability. Trying to come up with an example atm. – AlexNe Dec 07 '20 at 13:33

3 Answers3

1

Using eval is much slower as it needs to parse the string and interpret it whereas this step is not needed for deepcopy and you also require a conversion to string beforehand, which is not needed either with deepcopy.

Here is the result of both on my machine:

>>> timeit.timeit('copy.deepcopy(ls)', setup='import copy\nls = [[1,2], [3,4]]')
3.9644698999999974
>>> timeit.timeit('eval(str(ls))', setup='ls = [[1,2], [3,4]]')
8.982202999999998
ApplePie
  • 8,814
  • 5
  • 39
  • 60
0

Beside performance there are many good reasons not to use eval. For general downsides, have a look here.

Regarding your example particularly, I'd like to emphasize one example, where things go horribly wrong. Imagine having a class Foo, which stores an arbitrary value x:

class Foo:
    def __init__(self, x):
        self.x = x
    
    def __repr__(self):
        return f'{self.__class__.__name__}'

Now imaging, you create a list of objects of this class, e.g.:

foos = [Foo(i) for i in range(5)]

If you now use eval, trying to create a copy of your list, the result will not be equvialent to the original list:

test = eval(str(foos))

Instead of having a list of Foo objects, you will end up having a list of class types:

foos = [Foo(i) for i in range(5)]
print(foos)
# [Foo, Foo, Foo, Foo, Foo]
print(foos[0].x)
# 0

test = eval(str(foos))
print(test)
# [<class '__main__.Foo'>, <class '__main__.Foo'>, <class '__main__.Foo'>, <class '__main__.Foo'>, <class '__main__.Foo'>]
print(test[0].x)
# AttributeError: type object 'Foo' has no attribute 'x'
AnsFourtyTwo
  • 2,480
  • 2
  • 13
  • 33
0

In case you just got (non-complex) list of lists copying via list comprehension seems to be fastest and more secure than eval:

import timeit
import json
import ujson
import copy


def _eval(l):
    return eval(str(l))


def _copy(l):
    return copy.deepcopy(l)


def _json(l):
    return json.loads(json.dumps(l))


def _ujson(l):
    return ujson.loads(ujson.dumps(l))


def _comp(l):
    return [x[:] for x in l]


shortList = [[1, 2], [3, 4], [5, 6]]
longList = [[x, x + 1] for x in range(0, 50000)]

for lst in (shortList, longList):
    for func in ("_eval", "_copy", "_json", "_ujson", "_comp"):
        t1 = timeit.Timer(f"{func}({lst})", f"from __main__ import {func}")
        print(f"{func} ran:", t1.timeit(number=1000), "milliseconds")

Out (short list):

_eval ran: 0.009196660481393337 milliseconds
_copy ran: 0.005948461592197418 milliseconds
_json ran: 0.004726926796138287 milliseconds
_ujson ran: 0.0011531058698892593 milliseconds
_comp ran: 0.00045751314610242844 milliseconds

Out (long list):

_eval ran: 16.720303252339363 milliseconds
_copy ran: 7.898970659822226 milliseconds
_json ran: 2.1138144126161933 milliseconds
_ujson ran: 1.2348785381764174 milliseconds
_comp ran: 0.5541304731741548 milliseconds
Maurice Meyer
  • 17,279
  • 4
  • 30
  • 47
  • Thank you. Clearly list comprehension is the way to go when the sublists don't have lists in them. And ujson is the best for complex sublists. – Ivan Sycz Dec 07 '20 at 14:17