user24032725
user24032725

Reputation: 21

Make a descriptor that will turn some class attributes to immutable after initialization

I need to make a descriptor that will turn some class attributes to immutable after initialization

I need to make 1 class and 2 descriptors that will manage its attributes. The task is:

I've done a code for basic class and the price descriptor but stuck with the author\name one. Can u please help me solve this code?

My code is:

class PriceControl:
    """
    Descriptor which don't allow to set price
    less than 0 and more than 100 included.
    """

    def __init__(self):
        self.price = 0

    def __get__(self, instance, owner):
        return self.price

    def __set__(self, instance, value):
        if value < 0 or value > 100:
            raise ValueError('Price must be between 0 and 100.')
        self.price = value
        return self.price


class NameControl:

    """
    Descriptor which don't allow to change field value after initialization.
    """
    pass

    # def __set__(self, obj, value):
    #     if obj or value is None:
    #         return self
    #     elif obj is not None:
    #         raise ValueError(f'ValueError: Author can not be changed.')
    #     elif value is not None:
    #         raise ValueError(f'ValueError: Name can not be changed.')


class Book:
    author = NameControl()
    name = NameControl()
    price = PriceControl()

    def __init__(self, author, name, price):
        vars(self)['author'] = author
        self.name = name
        self.price = price

Expected result is :

>>> b = Book("William Faulkner", "The Sound and the Fury", 12)
>>> print(f"Author='{b.author}', Name='{b.name}', Price='{b.price}'")
Author='William Faulkner', Name='The Sound and the Fury', Price='12'

>>> b.price = 55
>>> b.price
55
>>> b.price = -12  # => ValueError: Price must be between 0 and 100.
>>> b.price = 101  # => ValueError: Price must be between 0 and 100.

>>> b.author = "new author"  # => ValueError: Author can not be changed.
>>> b.name = "new name"      # => ValueError: Name can not be changed.`

Upvotes: 1

Views: 92

Answers (1)

Dan Nagle
Dan Nagle

Reputation: 5425

Here's an implementation of the Book class along with the two descriptors:

Update: Modified PriceControl and NameControl as suggested by @chepner in the comments.

class PriceControl:
    def __set_name__(self, owner, name):
        self.name = "_" + name  # Use a different name to store the value

    def __get__(self, instance, owner):
        return getattr(instance, self.name, None)

    def __set__(self, instance, value):
        if not (0 <= value <= 100):
            raise ValueError("Price must be between 0 and 100")
        setattr(instance, self.name, value)

class NameControl:
    def __set_name__(self, owner, name):
        self.name = "_" + name  # Use a different name to store the value

    def __get__(self, instance, owner):
        return getattr(instance, self.name, None)

    def __set__(self, instance, value):
        if getattr(instance, self.name, None) is not None:
            raise ValueError(f"'{self.name[1:]}' attribute is immutable")
        setattr(instance, self.name, value)

class Book:
    price = PriceControl()
    author = NameControl()
    name = NameControl()

    def __init__(self, price, author, name):
        self.price = price
        self.author = author
        self.name = name

    def __repr__(self):
        return f"Book(author={self.author}, name={self.name}, price={self.price})"

Code to demonstrate use:

# Creating a book instance
book1 = Book(price=50, author="Jane Doe", name="Book Title")

print(book1)  # Output: Book(author=Jane Doe, name=Book Title, price=50)

# Changing the price (within range)
book1.price = 75
print(book1)  # Output: Book(author=Jane Doe, name=Book Title, price=75)

# Attempting to change author (immutable attribute)
try:
    book1.author = "John Smith"
except ValueError as e:
    print(e)  # Output: 'author' attribute is immutable

# Attempting to change name (immutable attribute)
try:
    book1.name = "New Book Title"
except ValueError as e:
    print(e)  # Output: 'name' attribute is immutable

# Attempting to set an invalid price (out of range)
try:
    book1.price = 200
except ValueError as e:
    print(e)  # Output: Price must be between 0 and 100

Output:

Book(author=Jane Doe, name=Book Title, price=50)
Book(author=Jane Doe, name=Book Title, price=75)
'author' attribute is immutable
'name' attribute is immutable
Price must be between 0 and 100

Upvotes: 2

Related Questions