1

I am implementing a Linked List and I have two classes, one for the list and another for the links. The link object has an instance attribute self.__next which is private. I want only the LinkedList object to be able to modify the value of this attribute, it should not be changed directly with the Link object.

If it is possible, I am guessing it would have to be implemented with an abstract class and/or inheritance.

class Link:
    def __init__(self):
        self.__next = True

    def setter(self):
        self.__next = False


class LinkedList():
    def setter(self):
        """Changes the value of 'self.__next' in the link"""
        pass
Chandral
  • 448
  • 1
  • 3
  • 19
  • 2
    Don't use setters. It's is not Pythonic. And python *doesn't have private variables*. The convention is to use a single underscore to mark attributes that are part of the non public API – juanpa.arrivillaga Jan 23 '20 at 13:16
  • Also, please explain your problem a bit more. What operations would be allowed and what operations would not? – Diptangsu Goswami Jan 23 '20 at 13:17
  • Is this helpful? https://stackoverflow.com/a/39716001/12094894 – Simon Crane Jan 23 '20 at 13:19
  • @DiptangsuGoswami So I want the value of ```self.__next``` in the ```Link``` object to be editable only by a method implemented in the ```LinkedList``` object. If I remove __ and make the variable public, I can store that link in a separate variable and it allows me to change it's value and hence break the link without the "knowledge" of the LinkedList object containing that link. Does that explain it better? – Chandral Jan 23 '20 at 17:49
  • https://stackoverflow.com/questions/7019643/overriding-properties-in-python Take a look at this question. – Diptangsu Goswami Jan 24 '20 at 06:48

1 Answers1

1

Okay. Before we tackle your problem at hand, let's find out if python actually has access modifiers like private.

Access specification is done by conventions:

  • A single underscore before a variable name would mean that the variable is used for some internal logic inside the class.

  • Two underscores before a variable name would ideally mean that the variable is meant to be private, however it's not.

>>> class Foo:
...     def __init__(self):
...         self.x = 10    # public
...         self._x = 20   # internal
...         self.__x = 30  # private
...
>>> f = Foo()
>>> f.x
10
>>> f._x
20
>>> f.__x
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'Foo' object has no attribute '__x'

You might be tempted to think that __x can't be accessed because it is private. But,

>>> f._Foo__x
30
>>> dir(f)
['_Foo__x', '__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', '_x', 'x']

The first value of dir(f) is _Foo__x, this is the variable __x. Python simply renames variables that have __ (2 underscores) before their names.

However, if you really want to prevent Link class objects from being able to modify link instance member, you can check where the setter method is being called from using the inspect module.

import inspect


class Link:

    def __init__(self, value=None, link=None):
        self.value = value
        self._link = link

    @property
    def link(self):
        return self._link

    @link.setter
    def link(self, value):
        caller = inspect.stack()[1].function
        if hasattr(LinkedList, caller):
            self._link = value
        else:
            raise AttributeError("Can't set value of link from class Link")


class LinkedList:

    def __init__(self):
        self.head = None

    def append_link(self, value):
        if not self.head:
            self.head = Link(value)
        else:
            t = self.head
            while t.link is not None:
                t = t.link
            t.link = Link(value)

    def __repr__(self):
        t = self.head
        list_values = []
        while t != None:
            list_values.append(str(t.value))
            t = t.link
        return f'[{", ".join(list_values)}]'


ll = LinkedList()
print(ll)
ll.append_link(10)
ll.append_link(20)
ll.append_link(30)
ll.append_link(40)
print(ll)

l = Link()
l.link = 'value'

Output:

$ python LinkedList.py
[]
[10, 20, 30, 40]
Traceback (most recent call last):
  File "LinkedList.py", line 55, in <module>
    l.link = 'value'
  File "LinkedList.py", line 20, in link
    raise AttributeError("Can't set value of link from class Link")
AttributeError: Can't set value of link from class Link
Diptangsu Goswami
  • 5,554
  • 3
  • 25
  • 36