Search code examples
pythonencapsulation

I can not understand why encapsuling does not work in my code


I am doing a course on python classes as a hobby and the topic is encapsulation and class values. I am trying to understand getters and setters. I've read tens of different explanations and I feel I understand how it works but I just can't get my simple code to function anything like I want.

The problem: I want to prevent the "pages" variable from being read outside the class. I think I have encapsulated it but it still works and I am totally stumped. Here is the code:

have updated the question here: What I want is that I could not edit the name or pages variables outside of the class after creating it. So for example after creating a class called stuff, this should not work:

stuff.pages = 50

But if I disable or edit the setters and getters the class breaks. If I remove the pages.setter I can not set the pages even inside the class at class creation.

So how do I set the encapsulation so that at class creation I can set pages but later it is not possible to change the pages outside the class using stuff.pages = 50 for example?

class Book:
    def __init__(self, name: str, pages: int):
        self.__name = name
        self.__pages = pages
    

    @property
    def name(self):
        return self.__name
    
    @name.setter
    def name(self, name):
        self.__name = name


    @property
    def pages(self):
        return None
        #return self.__pages NOTICE THIS

    @pages.setter
    def pages(self, pages):
        self.__pages = pages

    def __str__(self):
        return f'{self.name} ({self.__pages} kg)'


item = Book("BookXYZ", 420)
print(item.pages)

I set the encapsulated variables in the constructor. name and pages use the setter to set values and property to get them out of the class. For some reason item.pages still works. I need it not to work for the exercise. item.pages should not print anything but it shouldn't return None either as I have tried for my last desperate effort.

I've tried disabling the setters and getters one by one, removing and adding __ in front of variables and functions but I'm not getting anywhere. Please help.

More edits: (I think figured this out. The variable has __-prefix so it works directly, the function is not protected (I think this is the word) so it doesn't have __-prefix but then as a function it needs ()-suffix.

But why do I need to make additional functions when I have the @property functions already for the same variable? Shouldn't that work?

Something really weird happened and I have no idea why this even works. I commented out the setter functions and added two functions to the class. Then at str I changed the name to name(). Why does that work but making pages into pages() doesn't?! I know the () makes it a function call but why does it work for one but not for the other?

Here is the code.

class Book:
def __init__(self, name: str, pages: int):
    self.__name = name
    self.__pages = pages


@property
def name(self):
    return self.__name

@property
def pages(self):
    return self.__pages

def name(self):
    return str(self.__name)

def pages(self):
    return str(self.__pages)

def __str__(self):
    return f'{self.name()} ({self.__pages} kg)' #name()!!!!

and here is the code using it

stuff = Book("AAA", 20)
stuff2 = Book("BBB", 199)

print("book name:", stuff.name())
print("book pages:", stuff.pages())

print("book name:", stuff)
print("book name2:", stuff2)

Solution

  • It's not entirely clear what you do want to happen. But, it is possible to define a property that has no getter at all.

    class Book:
        def __init__(self, name: str, pages: int):
            self.__name = name
            self.__pages = pages
    
        def _pages_setter(self, pages):
            self.__pages = pages
    
        pages = property(None, _pages_setter)
    

    Now, an attempt to access item.pages will result in an AttributeError, rather than simply returning None, though assignments to item.pages will still work as usual.

    (There is a getter method, like setter, that you can use to explicitly set a getter for a property. It cannot, apparently, remove a getter by passing None as an argument.)