# Copy dictionary and modify it without affecting the original

**URL:** <https://forum.inductiveautomation.com/t/copy-dictionary-and-modify-it-without-affecting-the-original/62297>\
**Category:** Ignition\
**Tags:** ignition81\
**Created:** [July 26, 2022, 9:12am UTC](https://forum.inductiveautomation.com/t/copy-dictionary-and-modify-it-without-affecting-the-original/62297 "2022-07-26T09:12:13Z")\
**Posts on this page:** 20\
**Page:** 1

<div class="post-metadata">

**Author:** ![ISEMAREN\_AAG](https://avatars.discourse-cdn.com/v4/letter/i/82dd89/32.png) [@ISEMAREN\_AAG](https://forum.inductiveautomation.com/u/ISEMAREN_AAG)\
**Post date:** [July 26, 2022, 9:12am UTC](https://forum.inductiveautomation.com/t/copy-dictionary-and-modify-it-without-affecting-the-original/62297/1 "2022-07-26T09:12:13Z")

</div>

Good morning.

I **have copied a dictionary** and would like to **modify** the copy **without affecting the original**.  
That’s easy when the dictionary elements are integers or strings, but if there are **nested dictionaries** , when I modify the copied dictionary, the original is modified.  
For example (this is a simple example, actually I want to modify a “ **pen** ” (_ **PowerChart** _) ):

 ![image](https://us1.discourse-cdn.com/inductiveautomation/original/3X/a/c/ac2ab52de3704e616f472d733218b747a9534d28.png)

As we can see in the image, the element [“a”] and [“b”], are only modified in “dic2”. That is expected. But the element **[“c”][“c1”] is modified on “dic1” and “dic2”.**  
The only solution I’ve found is to copy [“c”] before modifying it, but it can be very tricky when you have to work with nested dictionaries, like the “ **pens** ” in a “ **PowerChart** ”.

 ![image](https://us1.discourse-cdn.com/inductiveautomation/original/3X/5/9/59aff0791d5cad062925dd1aa1593b4655608efe.png)

Best Regards.

---

<div class="post-metadata">

**Author:** ![Transistor](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/transistor/32/3380_2.png) [@Transistor](https://forum.inductiveautomation.com/u/Transistor)\
**Post date:** [July 26, 2022, 9:23am UTC](https://forum.inductiveautomation.com/t/copy-dictionary-and-modify-it-without-affecting-the-original/62297/2 "2022-07-26T09:23:09Z")

</div>

_This answer appears to show my incomplete understanding of this topic. See further answers below._

Try `deepcopy` instead.

> **[copy — Shallow and deep copy operations](https://docs.python.org/3/library/copy.html)**
>
> Source code: Lib/copy.py Assignment statements in Python do not copy objects, they create bindings between a target and an object. For collections that are mutable or contain mutable items, a copy ...

The manual points out that -

> - A _shallow copy_ constructs a new compound object and then (to the extent possible) inserts _references_ into it to the objects found in the original.
> - A _deep copy_ constructs a new compound object and then, recursively, inserts _copies_ into it of the objects found in the original.

---

<div class="post-metadata">

**Author:** ![ISEMAREN\_AAG](https://avatars.discourse-cdn.com/v4/letter/i/82dd89/32.png) [@ISEMAREN\_AAG](https://forum.inductiveautomation.com/u/ISEMAREN_AAG)\
**Post date:** [July 26, 2022, 10:06am UTC](https://forum.inductiveautomation.com/t/copy-dictionary-and-modify-it-without-affecting-the-original/62297/3 "2022-07-26T10:06:26Z")

</div>

![image](https://us1.discourse-cdn.com/inductiveautomation/original/3X/e/0/e08b762a287814fe6e30df6cad56511b9e467f00.png)

![image](https://us1.discourse-cdn.com/inductiveautomation/original/3X/b/7/b70fcb24a56cbb7e1fdd31bde040f6e0b4148098.png)

---

<div class="post-metadata">

**Author:** ![ISEMAREN\_AAG](https://avatars.discourse-cdn.com/v4/letter/i/82dd89/32.png) [@ISEMAREN\_AAG](https://forum.inductiveautomation.com/u/ISEMAREN_AAG)\
**Post date:** [July 26, 2022, 10:26am UTC](https://forum.inductiveautomation.com/t/copy-dictionary-and-modify-it-without-affecting-the-original/62297/4 "2022-07-26T10:26:50Z")

</div>

… so I have created **my own function to do deep copy of a dictionary.** :

 ![image](https://us1.discourse-cdn.com/inductiveautomation/original/3X/6/a/6ab099e0388919e5f8eff7cbf2d756a550aaab7b.png)

Now it works, thanks.

---

<div class="post-metadata">

**Author:** ![Transistor](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/transistor/32/3380_2.png) [@Transistor](https://forum.inductiveautomation.com/u/Transistor)\
**Post date:** [July 26, 2022, 11:03am UTC](https://forum.inductiveautomation.com/t/copy-dictionary-and-modify-it-without-affecting-the-original/62297/5 "2022-07-26T11:03:42Z")

</div>

Sorry about that. Maybe deepcopy was added after Python 2.7 (which is what Igntion’s is based on). This question on [StackOverflow](https://stackoverflow.com/questions/20699089/shallow-and-deep-copying) asks about its use on 2.7.5 but there is no mention of a problem.

---

<div class="post-metadata">

**Author:** ![JordanCClark](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/jordancclark/32/83362_2.png) [@JordanCClark](https://forum.inductiveautomation.com/u/JordanCClark)\
**Post date:** [July 26, 2022, 11:21am UTC](https://forum.inductiveautomation.com/t/copy-dictionary-and-modify-it-without-affecting-the-original/62297/6 "2022-07-26T11:21:18Z")

</div>

No, the OP tried to use deepcopy on a dict, which it doesn’t have. The correct form is to use the copy library, which has deepcopy()

```python
import copy

dic1 = {'a':1, 'b':2, 'c':{'c1':3, 'c2':{'c21':20}}}
dic2 = copy.deepcopy(dic1)

print 'Antes de modificar'
print dic1
print dic2

print '------------'
print 'Después de modificar'

dic2['a'] = 2
dic2['b'] = 4
dic2['c']['c1'] = 6
dic2['c']['c2']['c21'] = 40

print dic1
print dic2

```

Output:

```nohighlight
Antes de modificar
{'a': 1, 'b': 2, 'c': {'c1': 3, 'c2': {'c21': 20}}}
{'a': 1, 'b': 2, 'c': {'c1': 3, 'c2': {'c21': 20}}}
------------
Después de modificar
{'a': 1, 'b': 2, 'c': {'c1': 3, 'c2': {'c21': 20}}}
{'a': 2, 'b': 4, 'c': {'c1': 6, 'c2': {'c21': 40}}}
>>> 

```

---

<div class="post-metadata">

**Author:** ![ISEMAREN\_AAG](https://avatars.discourse-cdn.com/v4/letter/i/82dd89/32.png) [@ISEMAREN\_AAG](https://forum.inductiveautomation.com/u/ISEMAREN_AAG)\
**Post date:** [July 26, 2022, 12:51pm UTC](https://forum.inductiveautomation.com/t/copy-dictionary-and-modify-it-without-affecting-the-original/62297/7 "2022-07-26T12:51:51Z")

</div>

Thanks, but it doesn’t work when I apply it on a “pen”:

**Pen** :  
 ![image](https://us1.discourse-cdn.com/inductiveautomation/original/3X/3/5/35797a2a38e129a2bb8be84aec42694de038d4d9.png)

**Script** :

 ![image](https://us1.discourse-cdn.com/inductiveautomation/original/3X/9/f/9f40ec700191df0d54089cd76d8b59920db2eb4c.png)

---

<div class="post-metadata">

**Author:** ![bkarabinchak.psi](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/bkarabinchak.psi/32/83567_2.png) [@bkarabinchak.psi](https://forum.inductiveautomation.com/u/bkarabinchak.psi)\
**Post date:** [July 26, 2022, 2:53pm UTC](https://forum.inductiveautomation.com/t/copy-dictionary-and-modify-it-without-affecting-the-original/62297/8 "2022-07-26T14:53:02Z")

</div>

What is the type of `penEjemplo` ie `system.perpective.print(str(type(penEjemplo)))`? If its not a dict or a supported type for copy.deepcopy() I wouldn’t expect it to work.

---

<div class="post-metadata">

**Author:** ![lrose](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/lrose/32/62724_2.png) [@lrose](https://forum.inductiveautomation.com/u/lrose)\
**Post date:** [July 26, 2022, 3:34pm UTC](https://forum.inductiveautomation.com/t/copy-dictionary-and-modify-it-without-affecting-the-original/62297/9 "2022-07-26T15:34:03Z")

</div>

As @bkarabinchak.psi pointed out even though the structure “looks” like a `dict` it isn’t actually a `dict`.  
Actually the type of a perspective property when accessed from a script is:[com.inductiveautomation.perspective.gateway.script.PropertyTreeScriptWrapper.ObjectWrapper](https://files.inductiveautomation.com/sdk/javadoc/ignition81/8.1.1/com/inductiveautomation/perspective/gateway/script/PropertyTreeScriptWrapper.ObjectWrapper.html).

Thus deepcopy chokes when it runs into an object that it doesn’t know how to handle.

If you need a true deepcopy, then you have couple of options.

1. Write a function which recursively loops through the structure and correctly maps objects to dictionaries, and arrays to lists.
2. Encode to JSON and de-encode to guarantee you’re dealing with native Python objects

I have done both of those things in the past.

Note: Option 1 will work for “perspective properties”, it doesn’t account for other possible types that aren’t handled by deepcopy. The assumption I have made here is that if it isn’t an object, or array, then it is a basic type (string,integer,float,etc…). The function can/should be modified to handle other types if you need that particular thing.

Code for option 1:

```python
def recursiveCopy(original):
    from copy import deepcopy
    from com.inductiveautomation.ignition.common.script.abc import AbstractMutableJythonMap,AbstractMutableJythonSequence

    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)

```

Code for Option 2:

```python
from copy import deepcopy
pen = deepcopy(system.util.jsonDecode(system.util.jsonEncode(self.view.custom.penEjemplo)))

```

There are reasons to go with option 1 over option 2, but for what it looks like you’re trying to do either will work.

I will say though, that if I were trying to modify a pen in a power chart, I would just modify the pen, not sure why you need a copy for that.

---

<div class="post-metadata">

**Author:** ![ISEMAREN\_AAG](https://avatars.discourse-cdn.com/v4/letter/i/82dd89/32.png) [@ISEMAREN\_AAG](https://forum.inductiveautomation.com/u/ISEMAREN_AAG)\
**Post date:** [July 26, 2022, 3:57pm UTC](https://forum.inductiveautomation.com/t/copy-dictionary-and-modify-it-without-affecting-the-original/62297/10 "2022-07-26T15:57:40Z")

</div>

Yes, I have created my own function to deep copy.

I need to copy a “pen”, because from an example pen I dynamically create the “pens” of a PowerChart, only modifying the color, the name and the dataSource.

Thank you very much.

---

<div class="post-metadata">

**Author:** ![pturmel](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/pturmel/32/3295_2.png) [@pturmel](https://forum.inductiveautomation.com/u/pturmel)\
**Post date:** [July 26, 2022, 4:02pm UTC](https://forum.inductiveautomation.com/t/copy-dictionary-and-modify-it-without-affecting-the-original/62297/11 "2022-07-26T16:02:02Z")

</div>

Consider making your example pen a constant (dictionary) in a project script module. Then you can use `deepcopy` on it and modify as needed.

---

<div class="post-metadata">

**Author:** ![ISEMAREN\_AAG](https://avatars.discourse-cdn.com/v4/letter/i/82dd89/32.png) [@ISEMAREN\_AAG](https://forum.inductiveautomation.com/u/ISEMAREN_AAG)\
**Post date:** [July 26, 2022, 4:04pm UTC](https://forum.inductiveautomation.com/t/copy-dictionary-and-modify-it-without-affecting-the-original/62297/12 "2022-07-26T16:04:44Z")

</div>

It is a good idea, but “example pen” is a **param** on a PowerChart.

---

<div class="post-metadata">

**Author:** ![pturmel](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/pturmel/32/3295_2.png) [@pturmel](https://forum.inductiveautomation.com/u/pturmel)\
**Post date:** [July 26, 2022, 4:07pm UTC](https://forum.inductiveautomation.com/t/copy-dictionary-and-modify-it-without-affecting-the-original/62297/13 "2022-07-26T16:07:31Z")

</div>

I would suggest **not** having it as a param on your PowerChart.

---

<div class="post-metadata">

**Author:** ![lrose](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/lrose/32/62724_2.png) [@lrose](https://forum.inductiveautomation.com/u/lrose)\
**Post date:** [July 26, 2022, 4:11pm UTC](https://forum.inductiveautomation.com/t/copy-dictionary-and-modify-it-without-affecting-the-original/62297/14 "2022-07-26T16:11:51Z")

</div>

> [@ISEMAREN\_AAG](#):
>
> “example pen” is a **param**

There is nothing requiring you to have that parameter other than you. You can easily recreate the structure you need in a project script module and use it from there. Particularly if you plan to use this same method for more than one PowerChart component.

---

<div class="post-metadata">

**Author:** ![pascal.fragnoud](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/pascal.fragnoud/32/24728_2.png) [@pascal.fragnoud](https://forum.inductiveautomation.com/u/pascal.fragnoud)\
**Post date:** [July 27, 2022, 12:48pm UTC](https://forum.inductiveautomation.com/t/copy-dictionary-and-modify-it-without-affecting-the-original/62297/15 "2022-07-27T12:48:53Z")

</div>

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:

```python
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:

```python
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:

```python
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:

```python
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:

```python
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):

```python
def copyWeirdIgnitionType(obj):
    return {k: deepcopy(v) for k, v in obj.iteritems()}

```

---

<div class="post-metadata">

**Author:** ![victordcq](https://avatars.discourse-cdn.com/v4/letter/v/f6c823/32.png) [@victordcq](https://forum.inductiveautomation.com/u/victordcq)\
**Post date:** [July 27, 2022, 1:47pm UTC](https://forum.inductiveautomation.com/t/copy-dictionary-and-modify-it-without-affecting-the-original/62297/16 "2022-07-27T13:47:54Z")

</div>

> [@pascal.fragnoud](#):
>
> ```auto
> def copyWeirdIgnitionType(obj):
> return {k: deepcopy(v) for k, v in obj.iteritems()}
> 
> ```

I dont think that will work its doing the same as the default deepcopy minus one recursion.  
You need the function with AbstractMutableJythonMap and AbstractMutableJythonSequence as @Irose provided. or use jsonDe-/Encode

---

<div class="post-metadata">

**Author:** ![lrose](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/lrose/32/62724_2.png) [@lrose](https://forum.inductiveautomation.com/u/lrose)\
**Post date:** [July 27, 2022, 2:01pm UTC](https://forum.inductiveautomation.com/t/copy-dictionary-and-modify-it-without-affecting-the-original/62297/17 "2022-07-27T14:01:51Z")

</div>

> [@pascal.fragnoud](#):
>
> (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)

Not quite, they actually do have a `.items()` method. However, I believe that best practice is to use `.iteritems()` in this case as it is a generator and doesn't build the full list as `.items()` does, reducing memory usage. Probably doesn't make a huge difference in most cases, but still better in the edge cases where there could be a large number of key value pairs.

Note: Outside of Ignition, where Python 3 is a thing, `.iteritems()` has been removed and `.items()` actually returns a view object which also doesn't build the full list.

Otherwise, excellent explanation.

---

<div class="post-metadata">

**Author:** ![pascal.fragnoud](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/pascal.fragnoud/32/24728_2.png) [@pascal.fragnoud](https://forum.inductiveautomation.com/u/pascal.fragnoud)\
**Post date:** [July 27, 2022, 5:00pm UTC](https://forum.inductiveautomation.com/t/copy-dictionary-and-modify-it-without-affecting-the-original/62297/18 "2022-07-27T17:00:31Z")

</div>

If his function works by copying dicts, this should work. I assume `deepcopy` choked on that first object and the nested ones are handled just fine.  
Or they're not actually dicts but the type name contains "dict" and have a `copy` method... In which case, replace `dict` in the `isinstance` check by the actual type, and use `.copy()` on its items instead of `deepcopy`, but that seems unlikely.

> [@lrose](#):
>
> Not quite, they actually do have a `.items()` method. However, I believe that best practice is to use `.iteritems()` in this case as it is a generator and doesn’t build the full list as `.items()` does, reducing memory usage.

Alright. I mainly use python3 and went straight to what I know...

---

<div class="post-metadata">

**Author:** ![victordcq](https://avatars.discourse-cdn.com/v4/letter/v/f6c823/32.png) [@victordcq](https://forum.inductiveautomation.com/u/victordcq)\
**Post date:** [July 28, 2022, 6:24am UTC](https://forum.inductiveautomation.com/t/copy-dictionary-and-modify-it-without-affecting-the-original/62297/19 "2022-07-28T06:24:24Z")

</div>

> [@pascal.fragnoud](#):
>
> copying dicts

he is not copying dicts, your function doesnt work ☹  
 ![image](https://us1.discourse-cdn.com/inductiveautomation/original/3X/6/0/60e74f5d9c8870a2c6aff61152fa112085ad5a3b.png)

```auto
from com.inductive.ignition.common.script.abc import AbstractMutableJythonMap,AbstractMutableJythonSequence

```

has to be from

```auto
from com.inductiveautomation.ignition.common.script.abc import AbstractMutableJythonMap,AbstractMutableJythonSequence

```

btw

 ![image](https://us1.discourse-cdn.com/inductiveautomation/original/3X/1/d/1db3a7cbd621afd3a6f2e436adaba762a715b363.png)

---

<div class="post-metadata">

**Author:** ![pascal.fragnoud](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/pascal.fragnoud/32/24728_2.png) [@pascal.fragnoud](https://forum.inductiveautomation.com/u/pascal.fragnoud)\
**Post date:** [July 28, 2022, 7:51am UTC](https://forum.inductiveautomation.com/t/copy-dictionary-and-modify-it-without-affecting-the-original/62297/20 "2022-07-28T07:51:29Z")

</div>

Then I’m wondering how his original function worked, or rather, I’m wondering what type he was checking for with `if str(type(dicOriginen[key])).find("dict") > -1`.  
I assumed it was a dict, but I guess it might actually be a _‘fake’_ one that has “dict” in its name, and which also supports `.copy()` and `.items()`, but is not handled by `deepcopy`…

[Next page](https://forum.inductiveautomation.com/t/copy-dictionary-and-modify-it-without-affecting-the-original/62297.md?page=2)
