HiveBrain v1.2.0
Get Started
← Back to all entries
patternpythonMinor

Clone and replace attributes that are instances of a specific class

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
instancesarereplaceattributesthatspecificandclassclone

Problem

For a given object that may be an instance of a class, a built-in container or type, I would like to copy that object, but replace all instances of a given class with something else. I can do this with two calls to deepcopy as shown below, but there may be a more straightforward approach.

Create some complex nested object:

class T(object):
    pass

class A(object):
    def __init__(self):
        self.x = 10
        self.t1 = T()

class B(object):
    def __init__(self):
        self.a = A()
        self.y = 20
        self.t2 = T()

b = B()


Get a mapping from ids to objects:

visited = {}
copy.deepcopy(b, visited)


Create a custom dict type to replace instances of T with 100:

import collections

class Replacer(collections.MutableMapping):
    def __init__(self, idmap):
        self.store = dict()
        self.idmap = idmap
    def __getitem__(self, key):
        if isinstance(self.idmap[key], T):
            return 100
        return self.store[key]
    def __setitem__(self, key, value):
        self.store[key] = value
    def __delitem__(self, key):
        del self.store[key]
    def __iter__(self):
        return iter(self.store)
    def __len__(self):
        return len(self.store)


Copy and replace all T instances:

bb = copy.deepcopy(b, Replacer(visited))

In [121]: bb.t2
Out[121]: 100

In [122]: bb.a.t1
Out[122]: 100

Solution

You are overly complicating what you are trying to achieve by defining your Replacer class. You need two passes because your logic is:

  • store each id of custom objects into visited, associated to a copy of it;



  • change the value associated to ids of T objects into 100.



See how in 1. you already made a copy? You don't need that, you need to change how objects are copied in the first place.

The deepcopy function tries to copy a custom object x using one of 3 functions, in that order:

  • x.__deepcopy__;



  • x.__reduce_ex__(2);



  • x.__reduce__().



__reduce_ex__ and __reduce__ are a bit special and provided by object by default so better not to touch it. __deepcopy__, however, is copy.deepcopy own mechanism and should be used anytime you want to change how deepcopy should create new objects out of old ones.

If all you want to do is to turn all Ts into 100 when deepcopying an arbitrary object, then all you have to do is to implement T.__deepcopy__ in such a way:

class T(object):
    def __deepcopy__(self, memo): # expected signature for copy.deepcopy
        return 100


Every deepcopy will then turn each T into 100:

-
Ex1:

>>> b = [1,2,"hello",T(),('a',3,T()),T()]
>>> copy.deepcopy(b)
[1, 2, 'hello', 100, ('a', 3, 100), 100]


-
Ex2, using your A and B classes:

>>> b = B()
>>> bb = copy.deepcopy(b)
>>> bb.t2
100
>>> bb.a.t1
100


Note that in the context of your implementation and various calls, the T.__deepcopy__'s memo parameter would be an alias to visited, use it as you see fit if you want to implement custom logic. Read the last paragraph of the documentation for specific use cases.

Code Snippets

class T(object):
    def __deepcopy__(self, memo): # expected signature for copy.deepcopy
        return 100
>>> b = [1,2,"hello",T(),('a',3,T()),T()]
>>> copy.deepcopy(b)
[1, 2, 'hello', 100, ('a', 3, 100), 100]
>>> b = B()
>>> bb = copy.deepcopy(b)
>>> bb.t2
100
>>> bb.a.t1
100

Context

StackExchange Code Review Q#109938, answer score: 3

Revisions (0)

No revisions yet.