Node class with an accessible but immutable value

Hi,

In the nodes lesson quiz, the first question seems to imply that to create a “node class with an accessible but immutable value”, it’s enough to define a class with the following methods.

class Node:
    def __init__(self, value, link_node=None):
        self.value = value
        self.link_node = link_node

    def get_value(self):
        return self.value

    def get_link_node(self):
       return self.link_node
    
    def set_link_node(self, link_node):
        self.link_node = link_node

However, since there’s nothing stopping anyone from just, for example, doing the following,

new_node = Node(["test value", "test value2"])
new_node.value.append("test value3")
print(new_node.value) 
#output: ["test value", "test value2", "test value3"]

then what is actually immutable about it? Is there something I’m missing, or does the answer not really deliver on the question’s expectation?

1 Like

I think the key concept here which took me a while to get is what the specific semantic definition of “immutable” is. It has to do more with what exactly is being changed under the hood (e.g. when they say integers are immutable: assigning x the value of 10 and then changing it to 11, doesn’t mean ‘10’ itself undergoes a core change), and is better explained in documentation or articles like this (it’s always worth knowing because every language deals with these concepts):

https://medium.com/@meghamohan/mutable-and-immutable-side-of-python-c2145cf72747

3 Likes

Hmm, I think I understand it better now, thanks! I believe the key takeaway from that article you linked as it pertains to the exercise I was referring to is the following?

Custom classes are generally mutable. To simulate immutability in a class, one should override attribute setting and deletion to raise exceptions.

So, in order to really make the Node class immutable, we have to somehow make setting the attribute raise an exception? Just trying to make sure I understood it properly, because if not, I’ll have to read up more about it until I do.

1 Like

As I come back to this I see more and more how the wording is confusing.

I think what it’s going for is:

  1. Once you create an instance of node, there is no function to change it’s original value. (your append example concatenates to a list, but doesn’t change the original value).
  2. Pure immutability seems to be a trickier concept that it’s probably not trying to get at (just from searching a little: https://stackoverflow.com/questions/4843158/check-if-a-python-list-item-contains-a-string-inside-another-string)

This has been an interesting rabbit hole.

1 Like

Uhm, yeah.
That class says nothing about whether the value itself is mutable. What they mean to say is whether the attribute is mutable, that is, whether a different value may replace a current one. If you for example have a list as a value then you’d be able to do mutating list operations on that.

Also there are getters and setters, don’t need to use methods with that silly get/set naming.

using “value” as a “private” attribute name is asking for it to be accidentally poked at. you can’t truly hide things (even closures are visible from the outside) but you can at least name it so that it is clear that it isn’t supposed to be touched, for which the convention is to give the name a leading underscore.

class Node:
    def __init__(self, value):
        self._value = value

    @property
    def value(self):
        print('get')
        return self._value

    # @value.setter
    # def value(self, value):
    #     print('set')
    #     self._value = value


x = Node([])
x.value.append(3)  # the value itself is mutable
x.value = 3        # but the attribute is not
1 Like

This does seem like a broad and tricky subject with lots of different solutions. I found a few places online where they discuss this and did some reading: link1, link2, link3, link4.

For the first two links, I didn’t fully understand most of the answers and suggestions, and I don’t feel comfortable yet exploring them in depth, as it will probably take me a lot of time. The last two were a bit more simple. What I did learn is that to create immutability in a class, you can use __slots__, override the class’s __setattr__ to raise an exception and use super().__setattr__ to set its initial attributes in the class constructor. I also learned from your example that you can mark an attribute as something that’s not supposed to be tampered with using a conventional notation (_value).

I still don’t understand what is __slots__ or @property and there’s the difference between setting an attribute of the class itself from outside and setting an attribute of an instance of that class. Those are things I will have to explore more eventually.

Also there are getters and setters, don’t need to use methods with that silly get/set naming.

So I wasn’t wrong in thinking that those were kind of pointless :smile:

Thank you both for the discussion. I will be marking your post as the solution because it addressed my concern most directly.

Decorators aren’t doing anything too special, this:

@decorator
def f():
    return

is equivalent to:

def f():
    return
f = decorator(f)

the decorator is given the function/method and returns a replacement, for example, you could make it run twice:

def twice(f):
    def replacement(x):
        return f(f(x))
    return replacement

@twice
def add_two(x):
    return x + 1

print(add_two(8))  # 10

so what property does, is whatever boilerplate actions goes into creating a getter


anyway, in general, don’t use things that aren’t actually solving a problem for you. if something can be taken away without significant difference then it probably shouldn’t be there, pick the simplest thing that satisfies your needs. avoid thick layers of nothingness.

and if that strategy/attitude ends up giving you problems, good. now you have one to solve and now you know what you actually need.

1 Like

This topic was automatically closed 41 days after the last reply. New replies are no longer allowed.