Patt Mehta
Patt Mehta

Reputation: 4194

Restrict creation/setting of an attribute

How to restrict the creation of an attribute, outside its class/sub-class?

__all__ = ["Employee","Salary","Wage"]
##################################################################################################
class Person(object):
    def __init__(self,fname,lname,gender):
        self.__setfname(fname) # "We are all adults." vs. "Name mangling."
        self.__setlname(lname)
        self.__setgender(gender)
    def __setfname(self,fname): self.__fname = fname
    def __setlname(self,lname): self.__lname = lname
    def __setgender(self,gender): self.__gender = gender
    def getname(self): return "{} {}".format(self.__fname,self.__lname)
    def getformattedname(self):
        if(self.__gender.lower() == "m"):
            return "Mr. {}".format(self.getname())
        if(self.__gender.lower() == "f"):
            return "Ms. {}".format(self.getname())
        if(self.__gender.lower() == ""):
            return "{}".format(self.getname())
class Payment(object):
    def __init__(self,amount,currency="INR"): # currency="USD"
        self.__setamount(amount)
        self.__setcurrency(currency)
    def __setamount(self,amount): self.__amount = amount
    def __setcurrency(self,currency): self.__currency = currency
    def getamount(self): return "{}".format(self.__amount)
    def getformattedamount(self): return "{} {}".format(self.getamount(),self.__currency)
##################################################################################################
##################################################################################################
class Employee(Person):
    def __init__(self,fname,lname,gender): super(Employee,self).__init__(fname,lname,gender)
    def __str__(self): return self.getformattedname()
class Salary(Payment):
    def __init__(self,amount,currency="INR"): super(Salary,self).__init__(amount,currency)
    def __str__(self): return self.getformattedamount()
class Wage(Payment):
    def __init__(self,amount,currency="INR"): super(Wage,self).__init__(amount,currency)
    def __str__(self): return self.getformattedamount()
##################################################################################################

I'm OK with this:

e1._Person__fname = "Spam"
s1._Payment__amount = "1000000000000000"

but the following code creates new attributes:

e1.fname = "New"
s1.amount = -10

import re
from com.example.model import Employee,Salary,Wage

def printzip(l1,l2): print(list(zip([str(e) for e in l1],[str(e) for e in l2])))

(e1,e2) = (Employee("Sandeep","Mehta","m"),Employee("Varsha","Mehta","f"))
(s1,s2) = (Salary(3000,"USD"),Salary(3000,"USD"))

printzip([e1,e2],[s1,s2])
e1.fname = "New"
s1.amount = -3000
e1._Person__fname = "Spam"
s1._Payment__amount = "3000000000000000"

for e in enumerate([e for e in dir(e1) if not (re.search(r"^__.*__$",e))]): print(e)
for e in enumerate([e for e in dir(s1) if not (re.search(r"^__.*__$",e))]): print(e)
printzip([e1],[s1])

Python IDLE

Upvotes: 2

Views: 1075

Answers (4)

Ashwini Chaudhary
Ashwini Chaudhary

Reputation: 250891

You can do something like this:

class A:
    def __setattr__(self, attr, val):
        try :
            class_name, varname = attr.split('__', 1)
            if class_name == '_' + self.__class__.__name__:
                self.__dict__[attr] = val
            else:
                raise AttributeError
        except ValueError:
            raise AttributeError

Demo:

>>> %run so.py
>>> a = A()
>>> a._A__foo = 1
>>> a._A__bar = 2
>>> a._A = 2
Traceback (most recent call last):
  File "<ipython-input-28-eace128dbfc5>", line 1, in <module>
    a._A = 2
  File "/home/monty/py/so.py", line 10, in __setattr__
    raise AttributeError
AttributeError

>>> a.A__bar = 2
Traceback (most recent call last):
  File "<ipython-input-29-57210782cd6a>", line 1, in <module>
    a.A__bar = 2
  File "/home/monty/py/so.py", line 8, in __setattr__
    raise AttributeError
AttributeError

>>> a._A__spam = 3
>>> a.__dict__
{'_A__foo': 1, '_A__spam': 3, '_A__bar': 2}

Upvotes: 3

Daniel Roseman
Daniel Roseman

Reputation: 599490

Do not try to do this. It's not your business what someone else does with your class. You should document what attributes are expected to be available, and if someone wants to abuse it that's their problem, not yours.

"We're all consenting adults here" is the Pythonic philosophy.

Upvotes: 6

Eric
Eric

Reputation: 97575

Are you looking for properties?

class Foo(object):
    def __init__(self):
        self._amount = 0

    @property
    def amount(self):
        return self._amount


f = Foo()
print f.amount  # ok
f.amount = 100  # error

Upvotes: 3

kennytm
kennytm

Reputation: 523214

We could abuse __slots__, though the primary purpose of __slots__ is not to avoid creating extra attributes:

class Person(object):
    __slots__ = ('_Person__fname', '_Person__lname', '_Person__gender')
    ...

class Employee(Person):
    __slots__ = () # All subclasses also need define __slots__ 
    ...

e1._Person__fname = "Spam"  # Fine
e1.fname = "New"            # AttributeError: 'Employee' object has no attribute 'fname'

Note that with __slots__, the instance's __dict__ is not created, which may break some code using them.

Upvotes: 4

Related Questions