JoshJohnson
JoshJohnson

Reputation: 113

How to make immutable attributes in class?

Problem: I'm creating a class with attributes. Some of them should be only readable and some should be completely inaccessible when trying to print them out. I'm trying to achieve it through __setattribute__ and __getattribute__ functions (I just couldn't think of a better way to do that, if you have any suggestions on how to improve this, I would greatly appreciate it). The problem is that while __getattribute__ function works perfectly fine and raises needed exception, the __setattribute__ doesn't. The program simply passes it.

Code:

class Fruit:
  def __setattribute__(self, name, value):
    if name in ('exp_date'):
      raise AttributeError('Unable to edit')
    return __setattribute__(self, name, value)
  def __getattribute__(self, name):
        if name in ('subtype'):
            raise AttributeError('Unable to view this object')
        return __getattribute__(self, name)
  def __init__(self, sub:str = None, col:str = None, expd:int = None, pkp:int = None, amount:int = None):
    self.color, self.price_per_kilo = col, pkp
    self.amount = amount
    self.exp_date = expd
    self.subtype = sub
  def estimaterevenue(self):
    return self.price_per_kilo * self.amount

Test:

fruit= Fruit()
fruit.exp_date = 123 # __set__ is being passed for some reason
print(fruit.subtype) # AttributeError: Unable to view this object

Upvotes: 0

Views: 1470

Answers (3)

Daniel
Daniel

Reputation: 42748

__get_attribute__ and __set_attr_ are complicated to understand, better use a simple propert:

class Fruit:
    def __init__(self, subtype=None, color=None, exp_date=None, price_per_kilo=None, amount=None):
        self.color = color
        self.price_per_kilo = price_per_kilo
        self.amount = amount
        self._exp_date = exp_date
        self._subtype = subtype

    def estimaterevenue(self):
        return self.price_per_kilo * self.amount

    @property
    def exp_date(self):
        return self._exp_date
    

fruit= Fruit()
fruit.exp_date = 123 # AttributeError 
print(fruit.subtype) # AttributeError: Unable to view this object

Upvotes: 2

Piyush Singh
Piyush Singh

Reputation: 2962

The following implementation uses a counter so you can set the value once in the constructor but not subsequently.

class Fruit: 
  def __setattr__(self, name, value): 
    if name in ('exp_date') and self.count_exp_date>=1: 
      raise AttributeError('Unable to edit') 
    # return __setattribute__(self, name, value) 
    if name in ('exp_date'): 
        self.count_exp_date+=1 
    super().__setattr__(name, value)  
  def __getattribute__(self, name): 
        if name in ('subtype'): 
            raise AttributeError('Unable to view this object') 
        return super().__getattribute__(name) 
  def __init__(self, sub:str = None, col:str = None, expd:int = None, pkp:int = None, amount:int = None): 
    self.count_exp_date = 0 
    self.color, self.price_per_kilo = col, pkp 
    self.amount = amount 
    self.exp_date = expd 
    self.subtype = sub 
  def estimaterevenue(self): 
    return self.price_per_kilo * self.amount 

Upvotes: 0

quamrana
quamrana

Reputation: 39354

There seems to be some confusion about which dunder methods are defined. I don't know either, but I think this does what you want:

class Fruit:
    def __setattr__(self, name, value):
        print('set attribute', name)
        if name in ('exp_date'):
            raise AttributeError('Unable to edit')
        return super().__setattr__(name, value)

Update:

The above code does not work because exp_date cannot be set inside __init__().

This code works around that:

class Fruit:
  def __setattr__(self, name, value):
    #print('set attribute', name)
    if name in 'attr_gate' or self.attr_gate or name not in 'exp_date':
        return super().__setattr__(name, value)
    raise AttributeError('Unable to edit')
  def __getattribute__(self, name):
        #print('get attribute', name)
        if name in ('subtype'):
            raise AttributeError('Unable to view this object')
        return super().__getattribute__(name)
  def __init__(self, sub:str = None, col:str = None, expd:int = None, pkp:int = None, amount:int = None):
    self.attr_gate = True
    self.color, self.price_per_kilo = col, pkp
    self.amount = amount
    self.exp_date = expd
    self.subtype = sub
    self.attr_gate = False

Upvotes: 1

Related Questions