l4rmbr
l4rmbr

Reputation: 1307

How to transform attributes of a class instance recursively

I have a class Foo:

class Foo(object):
    def __init__(self, coord_tpl, coord_list, coord_dict, blah):
        # a single coord tuple
        # e.g. ((1, 2), (11, 22))
        self.coord_tpl = coord_tpl  

        # a list of coord tuple
        # e.g. [((1, 2), (11, 22)), ((3, 4), (33, 44))]
        self.coord_list = coord_list

        # a dict, whose value is a list of coord tuple
        # e.g. { 0: [((1, 2), (11, 22)), ((3, 4), (33, 44))],
        #        1: [((5, 6), (55, 66)), ((7, 8), (77, 88))] }
        self.coord_dict = coord_dict

        # some non-coord attributes
        self.blah = blah

I also have an axis translation function: coord_translate :

def coord_translate(old_coord):
    # apply some translation logic to old_coord (x_o, y_o)
    new_coord = ...
    return new_coord

Question

I have a Foo instance foo:

foo = Foo(...)

How can I translate the coordinate attributes(foo.coord_tpl, foo.coord_list and foo.coord_dict), while with other attributes(foo.blah) left intact, using coord_translate, in a more elegant way, instead of recursively handcrafting the translation logic?

UPDATE There does exist recursive structure in the form of attributes: fist simple tuple of coord, then list of tuple of coord, finally dict of list of tuple of coord.

To do the translation logic, I can reflect on the type of each attribute recursively, and apply coord_translate:

for k, v in foo.__dict__.items():
     if isinstance(v, tuple):
         setattr(foo, k, (coord_translate(v[0]), coord_translate(v[1])))
     elif isinstance(v, list):
         for c in v:
              ...
     elif isinstance(v, dict):
         ... 

I want to know if there some other tactics to do this, instead of using clumsy code like if isinstace(...)...elif isinstace(...)... else?

Upvotes: 1

Views: 204

Answers (3)

molbdnilo
molbdnilo

Reputation: 66449

I personally avoid type-switching and "attribute magic" (I've been bitten by too many bugs and stared at incomprehensible indirections for too long) and prefer to keep everything as explicit and straightforward as possible.

I would define a few helper functions:

def coord_translate(old_coord):
    # Dummy implementation
    x,y = old_coord
    return (x+1, y+1)

def translate_tuple(tpl):
    return tuple(map(coord_translate, tpl))

def translate_list(ls):
    return list(map(translate_tuple, ls))

def translate_dict(dct):
    return dict([(k, translate_list(v)) for (k,v) in dct.items()])

and then write

def translate(self):
    self.coord_tpl = translate_tuple(self.coord_tpl)
    self.coord_list = translate_list(self.coord_list)
    self.coord_dict = translate_dict(self.coord_dict)

You can of course wrap those helper functions in a type-switching function, if you want to.

Upvotes: 1

ItsMeTheBee
ItsMeTheBee

Reputation: 373

In many other languages like c++ you could overload the function (Define 3 functions named coord_translate but every funtion accepts a different data type) Because python is a strongly typed dynamic language i dont know of any way to do this.

You could use try except instead but as far as i know its

a) bad style

b) not better than your current solution

def coord_translate(self, input):
        try:
            coord_translate_dict(input)
        except ValueError:
            coord_translate_list(input)

In my opintion the cleanest solution would be to use a single data type and convert every input to match this type - i´m pretty sure there are already libraries to handle this.

Another approach would be something like this:

    def coord_translate_dict(self, dict):
        # apply some translation logic
        try:
            # do your stuff
            return True
        except:
            return False

    def coord_translate_list(self, list):
        # apply some translation logic
        try:
            # do your stuff
            return True
        except:
            return False

    def coord_translate_tuple(self, tuple):
        # apply some translation logic
        try:
            # do your stuff
            return True
        except:
            return False

    def coord_translate(self, input):
        if not coord_translate_dict(input) and not coord_translate_list(input):
            coord_translate_tuple(input)

Upvotes: 1

kederrac
kederrac

Reputation: 17322

you can try:

from abc import ABC, abstractmethod
from collections import defaultdict

class CoordType(ABC):
    registry_by_instance = defaultdict(list)

    @abstractmethod
    def coord_translate(self):
        pass

    @classmethod
    def apply_coord_translate(cls, instance):
        for attr in cls.registry_by_instance(instance):
            attr.coord_translate()

class CoordTpl(CoordType):
    def __init__(self, creation_instance, coord_tpl):
        self.coord_tpl = coord_tpl
        CoordType.registry_by_instance[creation_instance].append(self)

    def coord_translate(self):
        # your code to translate

class CoordList(CoordType):
    def __init__(self, creation_instance, coord_list):
        self.coord_list = coord_list
        CoordType.registry_by_instance[creation_instance].append(self)

    def coord_translate(self):
        # your code to translate

class CoordDict(CoordType):
    def __init__(self, creation_instance, coord_dict):
        self.coord_dict = coord_dict
        CoordType.registry_by_instance[creation_instance].append(self)

    def coord_translate(self):
        # your code to translate


class Foo(object):
    def __init__(self, coord_tpl, coord_list, coord_dict, blah):
        # a single coord tuple
        # e.g. ((1, 2), (11, 22))
        self.coord_tpl = CoordTpl(self.coord_tpl) 

        # a list of coord tuple
        # e.g. [((1, 2), (11, 22)), ((3, 4), (33, 44))]
        self.coord_list = CoordList(self, coord_list)

        # a dict, whose value is a list of coord tuple
        # e.g. { 0: [((1, 2), (11, 22)), ((3, 4), (33, 44))],
                 1: [((5, 6), (55, 66)), ((7, 8), (77, 88))] }
        self.coord_dict = CoordDict(self, coord_dict)

        # some non-coord attributes
        self.blah = blah

    def coord_translate(self):
        CoordType.coord_translate(self)

Upvotes: 2

Related Questions