13

I'm relatively new to Python and have problems with immutable variables.

I'm trying to change the value of a class attribute (e.g. car.color). The difficulty is, that I can not use the namespace of car for doing this.

Up to now I did not find a satisvying answer to my questions. In the code below I tried to summarize the possible solutions (workarrounds) I found and their disadvantages:

class Car:

    def __init__(self):
        self.color = "green"
        self.color_list = ["green"]
        self.color_attrib = "green"
        self.name = "VW Golf"
        """
        and many more attributes...
        """

    def makesetter(self, attribute):
        def set_value(value):
            attribute=value
        return set_value

    def set_color(self, value):
        "in this function I directly have access to car.color and can change its value: "
        self.color = value

    def set_attrib(self, attribute_string, value):
        setattr(self,attribute_string,value)

def change_attribute(attribute, value):
    "In this function I can not access car.color directly"
    attribute=value

def change_attribute_list(attribute, value):
    "In this function I can not access car.color directly"
    attribute[0] = value



if __name__ == "__main__":

    car1 = Car()

    change_attribute(car1.color, "red")
    print(car1.color)  # Color does not change because car1.color is immutable

    g = car1.makesetter(car1.color)
    g("red")
    print(car1.color)   # Color does not change because car1.color is immutable

    change_attribute_list(car1.color_list, "red")
    print(car1.color_list)  # Color changes but seems like a workarround
    # Disadvantage: in the namespace of car1, the user has to use a list to access a string value "car1.color_list[0]"

    car1.set_color("red")
    print(car1.color)  # Color changes but seems like a workarround
    # Disadvantage: Car needs a setter function for each attribute

    car1.set_attrib("color_attrib","red")
    print(car1.color_attrib)  # Color changes but seems like a workarround
    # Disadvantage: Attribute has to be passed as string & no auto completion while coding

Actually the function setattr() is internally exactly doing what I want. But it works with a string argument. I tried to look into this function but it seems to be written in C++.

So do I have to use C++ to solve this problem without a workarround? Or is there a Pythionic way of doing this?

Jan
  • 171
  • 1
  • 1
  • 8
  • You send the car object itself to the function and change the attribute there buy using the car object dict, or define a method inside the class which takes care of changing the attribute. – Pavan May 18 '17 at 11:51
  • 4
    Would it be possible for you to do `def change_attribute(obj, attribute, value): obj.__dict__[attribute] = value`? That would change the immutable object straight on it's source. And you'd only have to call it via `change_attribute(car1, 'color', 'blue')` – Torxed May 18 '17 at 11:51
  • Also what is your desired application? – Aurel Bílý May 18 '17 at 11:51
  • I would like to use this in a treeview model. I reimplemented the data() function to access all car attributes. The treeview shall always show exactly the data of car. Therefore I would like to save the reference to car.color like treeview.set_data(car.color, Qt.Display). When treeview gets data() for drawing it shall always show the data in car.color. But indeed it always shows "green", the initial value. – Jan May 18 '17 at 12:12
  • @ Pavan: that's possible but then I would have to write a function for each atribute. This seams like a workaround for me. @Torxed: possible but I have the disadvantage of using a string of the attribute. – Jan May 18 '17 at 12:15
  • What do you mean, you cannot use the namespace? What is preventing your from writing `car1.color = "red"` or `setattr(car1, "color", "red")`? – chepner May 18 '17 at 12:22

1 Answers1

11

The problem is you are trying to redefine the value of an instance from outside of the class. Since in __init__ you are defining your variables with self, they are only available for that instance. This is the point of a class - it's what makes them extensible and reusable.

Ideally, you would make a method within the class that would update those attributes, however, if you really need to update the class from an external function, you will have to define it as a class level variable. For instance:

class Car:

    def __init__(self):
        Car.color = "green"

can now be updated using:

def change_attribute(attribute, value):
    "In this function I can not access car.color directly"
    Car.color=value

outside of the class because you have not assigned it to one specific instance. Doing this presents a problem, however. Since we don't have a separate instance variable, if we try to re-instantiate the class, we are stuck with what was previously changed, i.e. if name == "main":

car1 = Car()
car2 = Car()
change_attribute(car1.color, "red")
print(car1.color)  # Prints red
print(car2.color)  # Prints red

change_attribute(car2.color, "blue")
print(car1.color)  # Prints blue
print(car2.color)  # Prints blue

This is why classes themselves should be self contained and are meant to be immutable - the instance itself should be changed.

Mogarrr
  • 373
  • 3
  • 11
  • Dear jlash93, thanks for the answer. I tried your example. But even if I make a class level variable, the color does not change by using change_attribute(). You said that ideally I would make a method to set the attributes. I have 25 attributes in the class. This means to build 25 setter functions and 25 getter functions. Can this be correct? – Jan May 18 '17 at 12:29
  • @Jan Why do you think you need setters and getters at all? In Python, you trust the user to read the documentation and not change values they aren't supposed to be changing. Just access the attributes directly. – chepner May 18 '17 at 12:36
  • @Jan It is possible to do it that way, but if it were me, I would create a dictionary of attributes that could be updated in a function for each command: `def __init__(self): self.Attributes = { 'Color': '', 'Make': '', 'Model': '' }` Then update it with a function: `def _updateColor(self): currentColor = self.Attributes['Color'] #This would get a list of colors # Do stuff to specify a new color self.Attributes['Color'] = newColor ` Then you can update everything and store it in a dictionary directly. – Mogarrr May 18 '17 at 13:27