Use @lrose 's function instead.
But let’s try to work out how to get from your function to his.
First, let’s see what can be improved independently of context:
- when iterating over a dict (let’s assume it’s called
d
), if you need the values, use
for value in d.values()
If you need the keys AND the values:
for key, value in d.items()
- when checking if a string contains a substring, don’t use
find
. Strings have methods just for this:
– check if it’s anywhere in a string: "bar" in "foobarbaz"
– check if a string starts with a substring: "foobarbaz".startswith("foo")
– check if it ends with a substring: "foobarbaz".endswith("baz")
- for type checking, use
isinstance(obj, type)
– you can check for several types at once: isinstance(obj, (dict, list))
which brings us to this version of the function:
def copiarProfundamente(dicOrigen, dicDestino):
for k, v in dicOrigen.items():
if isinstance(v, dict):
dicDestino[k] = v.copy()
copiarProfundamente(v, dicDestino[k])
else:
dicDestino[k] = v
Much clearer, don’t you think ? But we’re just getting started.
Now about the function itself:
- If your function should build a new object, let the function create and return it, don’t pass an empty object as parameter.
- make the base case clear
This would look like something like this:
def deepcopy(obj):
if isinstance(obj, dict):
return {k: deepcopy(v) for k, v in obj.items()}
return obj
So what happened there ?
The base case is the second return. If we don’t find a dict, we simply return the original object. Otherwise, call the function recursively for each key:value pair of the dict.
That’s a fundamental difference: We’re not iterating through the dict and using copy
on it’s values that are also dict, then calling the function again. We’re calling the function directly on each keys, and their copy is handled by the base case. The return of that call is assigned to the keys of our new dict.
- what if one of the value is a list ? lists are also not copied, but referenced. You can see it with a simple bit of code:
x = [[0] * 3] * 3
# x == [[0, 0, 0], [0, 0, 0], [0, 0, 0]]
x[0][0] = 1
# x == [[1, 0, 0], [1, 0, 0], [1, 0, 0]]
- This means the function needs to be able to take in lists as well, not only dicts, if we want to be able to make recursive calls on lists
This is simple to fix: We apply the same thing we did to dicts, but to lists:
def deepcopy(obj):
if isinstance(obj, dict):
return {k: deepcopy(v) for k, v in obj.items()}
if isinstance(obj, list):
return [deepcopy(v) for v in obj]
return obj
Now, we’re getting something similar to what @lrose suggested. The difference is that we’re doing this for dicts and lists, which copy.deepcopy
already does. We’re basically re-implementing it.
The next jump is to replace the base case by a call to copy.deepcopy
, and make the recursive calls conditions check for what copy.deepcopy
can’t handle. Which brings us exactly to @lrose 's function:
def recursiveCopy(original):
if isinstance(original,AbstractMutableJythonMap):
return {key:recursiveCopy(value) for key,value in original.iteritems()}
if isinstance(original,AbstractMutableJythonSequence):
return [recursiveCopy(item) for item in original]
return deepcopy(original)
(also note that obj.items()
has been changed to obj.iteritems()
, because this type of objects, which are not dicts, don’t have an .items()
method)
One last thing, that I maybe should have started with: Your function works and deepcopy
didn’t because you’re not actually feeding it a dict, but one of those type that deepcopy
can’t handle.
By not checking the type of the initial object, you’re skipping straight to copying the dicts that it contains, and that are actuals dicts.
Which means you could have written your function like this (I think):
def copyWeirdIgnitionType(obj):
return {k: deepcopy(v) for k, v in obj.iteritems()}