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