0

Hy,

I have a small class with only one attribute, which is a list with four elements. I want to make attributes for each object of the list with the help of property, but I don't want to write a setter and a getter method for each element. Currently I implemented it in the following way.

class MyClass:
    def __init__(self):
        self.my_list = [None in range(4)]

    def __get_my_list_x(self, x):
        return self.my_list[x]

    def __set_my_list_x(self, val, x):
        self.my_list[x] = val

    def get_my_list_0(self):
        return self.__get_my_list_x(x=0)

    def set_my_list_0(self, val):
        self.__set_my_list_x(val, x=0)

        # continue getter and setter methods for position 1, 2 and 3
        # of my list

    my_list_0 = property(get_my_list_0, set_my_list_0)
    my_list_1 = property(get_my_list_1, set_my_list_1)
    my_list_2 = property(get_my_list_2, set_my_list_2)
    my_list_3 = property(get_my_list_3, set_my_list_3)

At the moment I'm violating the Don't repeat yourself principle, because I have to write the getter and setter methods for my_list_0 to my_list_3. Is there a way to directly call the methods __get_my_list_x and __set_my_list_x in property() and specify the x argument?

I hope you guys get my question.

Have a nice day.

Peter Csala
  • 17,736
  • 16
  • 35
  • 75
crazyemod
  • 3
  • 1

1 Answers1

0

There are a lot of different solutions possible depending on your exact situation outside of this probably oversimplified example.

The best solution if you need to use actual attributes is probably to define your own custom descriptors (e.g. what property does under the hood):

class MyListIndexer:
    def __init__(self, index):
        self.index = index

    def __get__(self, instance, owner):
        return instance.my_list[self.index]

    def __set__(self, instance, value):
        instance.my_list[self.index] = value

class MyClass:
    def __init__(self):
        self.my_list = [None for _ in range(4)]
    my_list_0 = MyListIndexer(0)
    my_list_1 = MyListIndexer(1)

You can also add another parameter to MyListIndexer specifying the name of the attribute with help of getattr.


However, consider not using attributes at all and instead providing something like direct item access with __getitem__/__setitem__:


class MyClass:
    def __init__(self):
        self.my_list = [None for _ in range(4)]

    def __setitem__(self, key, value):
        self.my_list[key] = value

    def __getitem__(self, item):
        return self.my_list[item]

The extreme general solution that might have unexpected consequences and should only be used if there is no other solution is to use the __getattr__/__setattr__ functions:

class MyClass:
    def __init__(self):
        self.my_list = [None for _ in range(4)]

    def __getattr__(self, item):
        if item.startswith("my_list_"):
            val = int(item[8:])
            return self.my_list[val]
        else:
            return super(MyClass, self).__getattr__(item)

    def __setattr__(self, key, value):
        if key.startswith("my_list_"):
            ind = int(key[8:])
            self.my_list[ind] = value
        else:
            super(MyClass, self).__setattr__(key, value)
MegaIng
  • 7,361
  • 1
  • 22
  • 35