0

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)
vinter5
  • 37
  • 5
  • https://www.online-python.com/kl0VFPvnwc I've run your code here and it seems to work as expected – Norbiox Jun 13 '23 at 12:13
  • the exercise complains that it shouldn't print the name. The item.pages should not have access to that value. Why does it have access to that value? – vinter5 Jun 13 '23 at 12:15
  • It prints None but it still has access to that value. That part of my code is wrong. Putting None there does not make it encapsulated. How do I make "name" and "pages" encapsulated? – vinter5 Jun 13 '23 at 12:21
  • 1
    I don't understand this. Why have a getter function if you don't want to return anything? If a function does not return data explicitly Python defaults to None type as return type, and as a result print function prints the string representation of None type (which is "None"). – user47 Jun 13 '23 at 12:45
  • See: https://stackoverflow.com/questions/17576009/python-class-property-use-setter-but-evade-getter If you *really* want a "setter-only" attribute where the getter doesn't exist at all, there are ways to do it (see the top-voted answer there). It seems unlikely that an entry-level course expects you to deploy those, though. – slothrop Jun 13 '23 at 13:21
  • 1
    Your pages method does not have a return value hence item.pages will always be None. So you are hiding the value of pages even if the name is accessible outside the Class. – user19077881 Jun 13 '23 at 13:25
  • I have added a better question to the topic. I have trouble asking the right question because I just don't understand this topic. – vinter5 Jun 14 '23 at 00:48
  • In your updated code, what happens if you replace `self.__pages` with `self.pages()` in the `__str__` method? You say this doesn't work, but what exactly is the result? – slothrop Jun 14 '23 at 18:20

2 Answers2

2

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.)

chepner
  • 497,756
  • 71
  • 530
  • 681
0

The concept of a private variable accessible inside a class only by itself doesn't exist as a rule in Python. It is typical and generally accepted to use underscore(s) before the variable to signify to the user that it is "private", but it can still be used. For more information check out this answer or this explaination of using underscores in Python. This website might be helpful too! From what I can tell, you are doing everything right so far, good luck with future learning!