✦ Python Intermediate & OOP Intermediate Python

L2 · Concept 06 · OOP Fundamentals

Protect book data.
Keep valid state.

Mini Library Manager now validates a book’s publication year through a clear property interface.

Learning goalUse _year, @property, and a setter to validate a positive integer year without leaving a Book in an invalid state.
01 Learn02 Try03 Predict

Learning as a guest

Learning as a guest? Save your progress!

Create a free account to learn with Sloby AI, check your understanding, and track your progress.

The entire platform is free throughout 2026. Foundations stays free forever.

Control how a book changes.

Encapsulation groups data with the rules that protect it. In Python, a leading underscore such as _year is a convention for internal use, not a strict access ban.

class Book:
    def __init__(self, title, author, year):
        self.title = title
        self.author = author
        self.is_available = True
        self.year = year

    @property
    def year(self):
        return self._year

    @year.setter
    def year(self, value):
        if not isinstance(value, int) or value <= 0:
            raise ValueError("year must be a positive integer")
        self._year = value

    def borrow(self):
        self.is_available = False

    def return_book(self):
        self.is_available = True

book = Book("Dune", "Frank Herbert", 1965)
try:
    book.year = 0
except ValueError:
    pass
print(book.year)

self.year = year calls the setter during creation, so validation also protects the initial value. The setter checks before assigning self._year.

1965

The failed assignment raises ValueError before _year changes. After catching the error, the earlier valid year remains 1965.

↳

Property pattern: Read book.year through the getter. Assign book.year = value through the setter. Keep _year as the internal stored value.

Validate before changing state
class Book:
    def __init__(self, title, author, year):
        self.title = title
        self.author = author
        self.is_available = True
        self.year = year

    @property
    def year(self):
        return self._year

    @year.setter
    def year(self, value):
        if not isinstance(value, int) or value <= 0:
            raise ValueError("year must be a positive integer")
        self._year = value

    def borrow(self):
        self.is_available = False

    def return_book(self):
        self.is_available = True

book = Book("Dune", "Frank Herbert", 1965)
try:
    book.year = 0
except ValueError:
    pass
print(book.year)

The public year property is easy to use, while its setter rejects zero, negative, or non-integer years.

A careful setter
@year.setter
def year(self, value):
    if not isinstance(value, int) or value <= 0:
        raise ValueError("year must be a positive integer")
    self._year = value

Validate first and assign second. That order means invalid input cannot overwrite a valid stored value.

Follow a protected update

  1. 1. Use the public propertybook.year = 1965 calls the setter instead of writing directly to _year.
  2. 2. Validate the new valueThe setter accepts only positive integers and raises ValueError otherwise.
  3. 3. Store after validationOnly a valid value reaches self._year.
  4. 4. Keep the earlier stateA failed assignment leaves the book’s previously valid year unchanged.

Common mistakes

  • Writing self.year inside the setterUse self._year in the setter; assigning self.year there calls the setter again.
  • Assigning before validationCheck the value before changing _year so invalid state is never stored.
  • Treating _year as private enforcementThe underscore is a convention that communicates internal use; Python does not block direct access.
  • Forgetting validation in __init__Assign self.year = year in __init__ so creation uses the same setter rule.

Python Playground

Loading Python runtime…

Try a valid year update, then try an invalid value inside try/except and inspect the saved year.

book_property.pyPython
OutputWaiting for Python
The browser runtime is loading. You can still edit the example code.

Pyodide runs Python in a browser Web Worker. It is stopped and restarted if code keeps running. Browser execution is not a complete security boundary; do not enter secrets or rely on it for untrusted code isolation.

What will this program print?

The setter rejects zero before it can replace the existing valid year.

class Book:
    def __init__(self, title, author, year):
        self.title = title
        self.author = author
        self.is_available = True
        self.year = year

    @property
    def year(self):
        return self._year

    @year.setter
    def year(self, value):
        if not isinstance(value, int) or value <= 0:
            raise ValueError("year must be a positive integer")
        self._year = value

    def borrow(self):
        self.is_available = False

    def return_book(self):
        self.is_available = True

book = Book("Dune", "Frank Herbert", 1965)
try:
    book.year = 0
except ValueError:
    pass
print(book.year)
Choose the output

Practice independently

Mini Challenge — Validate a rating

Add one simple validated property to a Book without bypassing its internal state.

Your goalCreate a rating property that accepts only integers from 1 to 5 and raises ValueError for other values.

rating_property_challenge.py

Optional hints
  • Use @property above def rating(self), then @rating.setter above def rating(self, value).
  • Check isinstance(value, int) and 1 <= value <= 5 before assigning self._rating.
Ask Sloby for a hint
Sloby, the Python AI Tutor mascot

Your learning mentor

Sign in to ask Sloby

Create a free account or sign in to get AI guidance while you learn.

Sign in to check your understanding

Create a free account or sign in to receive AI feedback and save your learning progress.

Python AI Tutor · Learn by understanding, not just by running code.