Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How To Limit Properties Available On a Python Class

Tags:

python

Take the following class

class Person(object):

def __init__(self, first_name, last_name):
    self.first_name = first_name
    self.last_name = last_name

How can I prevent the following usage?

p1 = Person('Foo', 'Bar')
p1.firstname='Fooooooo'

The code above will execute successfully in Python, however, a mistake was made with the name of the property, i.e. its missing _ between first and name

UPDATE: This sounds like "Monkey Patching", why do I want to do this?

My intention is to simply help avoid the user from setting the wrong property, have the code execute, and see unexpected behavior and not be aware of the mistake immediately.

What does the Pythonic way recommend here?

like image 423
Danish Avatar asked Apr 29 '13 14:04

Danish


1 Answers

First of all, it's almost always a bad idea to do such a thing. If only reason why you want that is making sure you don't make typos - there are better tools for that (think IDE or pylint). If you are a 100% positive that you need such a thing, here are two ways to do it:

First way - you can do this with using __setattr__ method. See python __setattr__ documentation

class Person(object):

    def __init__(self, first_name, last_name):
        self.__dict__['first_name'] = first_name
        self.__dict__['last_name'] = last_name
    def __setattr__(self, name, value):
        if name in self.__dict__:
            super(Person, self).__setattr__(name, value)
        else:
            raise AttributeError("%s has no attribute %s" %(self.__class__.__name__, name))

and output:

In [49]: a = Person(1, 2)

In [50]: a.a = 2
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
/usr/local/lib/python2.7/dist-packages/django/core/management/commands/shell.pyc in <module>()
----> 1 a.a = 2

/usr/local/lib/python2.7/dist-packages/django/core/management/commands/shell.pyc in __setattr__(self, name, value)
      8             super(Person, self).__setattr__(name, value)
      9         else:
---> 10             raise AttributeError("%s has no attribute %s" %(self.__class__.__name__, name))

AttributeError: Person has no attribute a

Alternatively, you can do this using __slots__(python __slots__ documentation):

class Person(object):
    __slots__ = ("first_name", "last_name")

    def __init__(self, first_name, last_name):
        self.first_name = first_name
        self.last_name = last_name

output:

In [32]: a = Person("a", "b")

In [33]: a.first_name
Out[33]: 'a'

In [34]: a.a = 1
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
/usr/local/lib/python2.7/dist-packages/django/core/management/commands/shell.pyc in <module>()
----> 1 a.a = 1

AttributeError: 'Person' object has no attribute 'a'

The first way is more flexible as it allows hacking this code even further through using __dict__ directly, but that would be even more wrong than it is now. Second approach preallocates space for certain number of instance variables (references), which means less memory consumption.

like image 129
Krzysztof Bujniewicz Avatar answered Oct 27 '22 05:10

Krzysztof Bujniewicz