2

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

Patt Mehta
  • 4,110
  • 1
  • 23
  • 47

4 Answers4

6

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.

Daniel Roseman
  • 588,541
  • 66
  • 880
  • 895
  • (y) @"It's not your business what someone else does with your class." But... it is my business if someone else starts abusing the attributes such that the models/databases start getting affected. – Patt Mehta Sep 02 '13 at 17:14
  • I am of the same opinion as Daniel. If you buy a car and manipulate the breaks in such a manner that they don't work anymore, this won't hardly be the problem of the automobile manufacturer but your own. If you use undocumented features of a library and suddenly those aren't supported anymore, it is your business and not the business of the library's authors. If a user abuses your code, it is his business not yours. "Use code only as intended". Just my two cents. – Hyperboreus Sep 02 '13 at 17:43
  • @GLES are your coworkers really so untrustworthy you can't rely on them not to hack the database? – Daniel Roseman Sep 02 '13 at 18:05
  • @GLES: If they're wanting to hack the database, surely they could just modify the source of your class? Also, `object.__setattr__(your_class_instance, 'whatever_key_i_like', value)` is going to ruin whatever you try to do. – Eric Sep 02 '13 at 18:43
4

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.

kennytm
  • 510,854
  • 105
  • 1,084
  • 1,005
3

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}
Ashwini Chaudhary
  • 244,495
  • 58
  • 464
  • 504
3

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
Eric
  • 95,302
  • 53
  • 242
  • 374