Dynamic use of functions inside __getitem__ method

336 views Asked by At

Suppose that you have a python class MyObject such that instances contain a list of data and that the MyObject class as a __getitem__ method defined as follows:

def __getitem__(self, index):
   return self.data[index]

Suppose that we wish to modify the above __getitem__ method to something like

def __getitem__(self, index):
   return self.data[my_function(index)]

where my_function is some function that transforms an alternative indexing system (say tuples of integers into the list index system - non-negative integers). Here my_function might be self.map or OtherClass.map or something else that makes the above code valid python code. We want to enable the user to change this function - including using the identity function (i.e. no conversion). This can be done easily enough.

But here is what I want to do. Suppose that I have a bunch of instances $p_1,p_2,..,p_k$ of 'MyObject' class using some map map_1 and I have another bunch of instances $q_1,q_2,...,q_t$ using some map map_2.

  1. I want to change the map that the instances $p_1,p_2,..,p_k$ use from map_1 to map_2. I do not want to do this by looping over each instance $p_1,p_2,..,p_k$ and setting the index map to map_2 but rather only doing $O(1)$ work (i.e. not $O(k)$ work).

  2. Then I want to have all $p_1,p_2,..,p_k,q_1,q_2,...,q_t$ instances using map_2.

  3. Then I want to change these instances to use a new map map_3 where it again only takes the same amount of constant time to change.

I can see how to do this is C/C++ using references but not in python. The user provides the map functions and there can be many. The best I can get is as follows:

# A simple Map class to hold maps
class MapDict:
    def __init__(self):
       self.loaded_maps = dict()

    def make_map(self, fid):
        def index_funct(index):
            return self.loaded_maps[fid](index)
        return index_funct

# A simple class for data
class MyObject:
    def __init__(self,data):
        self.data = data
        self._map = self._identity_map

    def _identity_map(self, index):
        return index

    def set_map(self, input_map):
        self._map = input_map

    def __getitem__(self, index):
        return self.data[self._map(index)]

# Create some MyObjects with sample data
p1=MyObject(range(1000))
p2=MyObject(range(1000))
q1=MyObject(range(1000))
q2=MyObject(range(1000))

#Create a class to hold the maps
map_dict = MapDict()

# Define a few map functions to use in indexing
def map1(index):
    return index[0]+index[1]

def map2(index):
    return index[0]*index[1]

def map3(index):
    return index[0]+10*index[1]

# Load the maps into the map dict class
map_dict.loaded_maps[1] = map1
map_dict.loaded_maps[2] = map2

# Set the maps for the MyObjects
p1.set_map(map_dict.make_map(1))
p2.set_map(map_dict.make_map(1))
q1.set_map(map_dict.make_map(2))
q2.set_map(map_dict.make_map(2))

# Now change maps for p1, p2
map_dict.loaded_maps[1] = map2

# Now change maps for p1 p2 q1 q2
map_dict.loaded_maps[1] = map3
map_dict.loaded_maps[2] = map3

The problem with this approach is that I now have two instances of map3 in a dictionary rather than a single location. So if I want to change map3 to map4 for all instances using map3 then I have so change two locations. A guess that I could provide another list/dictionary on top of this to keep track but this seems overkill. Also I want to make sure that _getitem__ remains very fast (relative to the map function chosen) as I expect it to be used a lot.

I expect that there are a much better way to do this so ideas are welcome.

1

There are 1 answers

0
Drew E On

If I'm understanding correctly, you'd like to be able to switch the -getitem- method for a group of instances. I think having a separate class to store the mapping functions seems redundant when your MyObject class could store it for groups of its instances and dispatch the function.

class MappedList:
    
    mappers = {}
    
    def __init__(self, data, group, mapper=None):
        self.data = data
        self.group = group
        # add mapping function to dict if creating new group
        if self.group not in type(self).mappers.keys():
            type(self).mappers[self.group] = mapper
        
    def __getitem__(self, item):
        return self.data[type(self).mappers[self.group](item)]
        
    @classmethod
    def edit_group_mapper(cls, group, mapper):
        # reset groups getter function
        cls.mappers[group] = mapper

Functions for remapping the index into the data:

def map1(item):
    return item + 1

def map2(item):
    # should catch if remapped item not in data here
    return item + 2

Making a few groups with this:

p1 = MappedList(['a', 'b', 'c'], group=1, mapper=map1)
p2 = MappedList(['d', 'e', 'f'], group=1)
m1 = MappedList(['g', 'h', 'i'], group=2, mapper=map2)
m2 = MappedList(['j', 'k', 'l'], group=2)

Testing first mapper function and testing the changed mapper function:

print(p1[0], p2[0])

MappedList.edit_group_map(group=1, mapper=map2)

print(p1[0], p2[0])

Returns:

b e
c f