Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to protect againt typos when setting value for class members?

Tags:

python

Consider the following example:

class A():
    def __init__(self):
        self.veryImportantSession = 1

a = A()
a.veryImportantSession = None # ok

# 200 lines below
a.veryImportantSessssionnnn = 2 # I wanna exception here!! It is typo!

How could I make it so that an exception will be raised if I try to set a member that is not set in __init__?

Code above won't fail when it is executed, but gives me a fun time to debug the problems.

Like with str:

>>> s = "lol"
>>> s.a = 1
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'str' object has no attribute 'a'

Thanks!

like image 482
lstipakov Avatar asked Jun 20 '10 12:06

lstipakov


3 Answers

You could override __setattr__ to only allow attribute names from a defined list.

class A(object):
    def __setattr__(self, name, value):
        allowed = ('x',)
        if name in allowed:
            self.__dict__[name]  = value
        else:
            raise AttributeError('No attribute: %s' % name) 

In operation:

>>> a = A()
>>> a.x = 5
>>> a.other = 5
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "myc.py", line 7, in __setattr__
    raise AttributeError('No attribute: %s' % name)
AttributeError: No attribute: other   

However, as msw has commented, attempts to make Python behave more like Java or C++ are usually a bad idea and will lead to losing lots of the benefits that Python provides. If you are concerned about making typos that might be missed then you are much better spending time writing unit tests for your code than trying to lock down the usage of your classes.

like image 162
mikej Avatar answered Oct 21 '22 10:10

mikej


You can define a class variable called __slots__. See the Language Reference for more information.

__slots__ only work in new-style classes, so you need class A(object) instead of class A in this example.

class A(object):
    __slots__ = ['x']
    def __init__(self):
        self.x = 1

>>> a = A()
>>> a.x = 2
>>> a.y = 2
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'A' object has no attribute 'y'
like image 21
Baffe Boyois Avatar answered Oct 21 '22 10:10

Baffe Boyois


The best way to protect against typos is to use a tool such as pyflakes or pylint -- these will go through your code and let you know if you have a variable a.superImportantSesssssionnnn that is not ever used again, as well as many other things.

If you insist on doing it in code you have two options:

  • (ab)use __slots__ -- the primary purpose of __slots__ is memory optimization for those times when you will have hundreds, thousands, or millions of objects and you need to keep your objects' footprints as small as possible. If you go this route you should make sure and include __weakref__ as one of the allowed names so weak references will continue to work.

  • (ab)use __setattr__ -- the primary purpose of __setattr__ is to allow code to be run to validate, manipulate, or otherwise take the data given and put it in the correct format for storage

To reiterate, the above two options are not intended to be used as spell-checkers, and your efforts to limit the usefulness of your code by disallowing every possible attribute name except for those few you use yourself may not be appreciated when someone is trying to support a use-case you did not anticipate.

like image 23
Ethan Furman Avatar answered Oct 21 '22 11:10

Ethan Furman