Reputation: 11
So my task is:
*You must implement the class Book with the attributes price, author, and name.
The author and name fields have to be immutable; The price field may have changes but has to be in the 0 <= price <= 100 range. If a user tries to change the author or name fields after an initialization or set price is out of range, the ValueError should be raised.
Implement descriptors PriceControl and NameControl to validate parameters.*
Example:
>>> 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.
My decision 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
class NameControl:
"""
Descriptor which don't allow to change field value after initialization.
"""
pass
class Book:
author = NameControl()
name = NameControl()
price = PriceControl()
def __init__(self, author, name, price):
self.author = author
self.name = name
self.price = price
So as you see I implemented a solution for price control but I can't imagine how to make descriptor for author/name control. Can you help me please?
Upvotes: 1
Views: 631
Reputation: 1
Score: 100
class PriceControl:
__value: float
def __init__(self, __value: float = None):
self.__value = __value
def __get__(self, instance, owner):
return self.__value
def __set__(self, instance, value):
if value < 0 or value > 100:
raise ValueError(f"ValueError: Price must be between 0 and 100.")
self.__value = value
def __str__(self):
return str(self.__value)
class AuthorControl:
__value: str
def __init__(self, __value: str = None):
self.__value = __value
def __get__(self, instance, owner):
return self.__value
def __set__(self, instance, value):
if self.__value is not None:
raise ValueError(f"ValueError: Author can not be changed.")
self.__value = value
class NameControl:
__value: str
def __init__(self, __value: str = None):
self.__value = __value
def __get__(self, instance, owner):
return self.__value
def __set__(self, instance, value):
if self.__value is not None:
raise ValueError(f"ValueError: Name can not be changed.")
self.__value = value
class Book:
author = AuthorControl()
name = NameControl()
price = PriceControl()
def __init__(self, author, name, price):
self.author = author
self.name = name
self.price = price
Upvotes: 0
Reputation: 532093
Descriptors don't provide an automatic way to allow exactly one assignment; you'll still need to define a setter to determine if an assignment has taken place yet.
Note that in both cases, you should attach the value to the instance
received as an argument, not the descriptor itself, or else all instances of Book
will share the same name, author, and price.
# Hard-coded assumption that PriceControl will operate on
# an attribute named _price; see NameControl for a way to
# derive an attribute name from the name of the descriptor itself.
class PriceControl:
"""
Descriptor which don't allow to set price
less than 0 and more than 100 included.
"""
def __get__(self, instance, owner):
return instance._price
def __set__(self, instance, value):
if value < 0 or value > 100:
raise ValueError('Price must be between 0 and 100.')
instance._price = value
class NameControl:
# This method is invoked at class creation time,
# with the name of the class attribute bound to the
# descriptor instance passed as the 'name' argument.
# I.e.,
#
# class Book:
# author = NameControl()
#
# causes
#
# Book.author.__set_name__('author')
#
# to be called.
def __set_name__(self, name):
self.public_name = name
self.private_name = "_" + name
def __get__(self, instance, owner):
return getattr(instance, self.private_name)
def __set__(self, instance, value):
# TODO: check if instance already has the private
# instance attribute set below.
setattr(instance, self.private_name, value)
(You may want to modify both __get__
methods to handle them being called before __set__
has a chance to create the underlying instance attribute.)
Upvotes: 0