0

In Python (I'm talking 2 here, but would be interested to know about 3 too) is there a way to define in advance a list of all instance variables (member fields) you want available i.e. make it an error to use one you've not defined somewhere?

Something like

class MyClass(object):
    var somefield
    def __init__ (self):
        self.somefield = 4
        self.banana = 25      # error!

A bit like you do in Java, C++, PHP, etc

Edit:

The reason I wanted this kind of thing was to spot early on using variables that hadn't been setup initially. It seems that a linter will actually pick these errors up without any extra plumbing so perhaps my question is moot...

Seb
  • 6,507
  • 2
  • 19
  • 23
  • Why would you want this? – Dhara Feb 07 '13 at 15:10
  • Why would you want to do such a thing? – Matt Seymour Feb 07 '13 at 15:10
  • @mattwritescode, Dhara: Using `__slots__` can make the code faster in certain situations. Beyond that, it can enforce certain OOP practices. See [here](http://stackoverflow.com/questions/472000/python-slots) – David Robinson Feb 07 '13 at 15:13
  • As I understand Python there is no safe way but you could "protect" your variables by writing properties for them and check the existence before you set a variable. I could also think of a redefinition of the set_attr method of an object initiating with a list of "allowed" variables. But this are only short thoughts about this. – wagnerpeer Feb 07 '13 at 15:18
  • I'd like this as it seems like the kind of thing a linter or compiler could find early on if I declare the list of allowed members up front. Saves any typos, copy-pasta errors, etc before runtime - crash early, right? – Seb Feb 07 '13 at 15:18

2 Answers2

5

Why yes, you can.

class MyClass(object):
    __slots__ = ['somefield']
    def __init__ (self):
        self.somefield = 4
        self.banana = 25      # error!

But mind the caveats.

Ignacio Vazquez-Abrams
  • 776,304
  • 153
  • 1,341
  • 1,358
  • Simultaneous answer! :) -- But yours has code :(. I suppose I'll delete mine then ... – mgilson Feb 07 '13 at 15:12
  • @Dhara why is using slots a bad idea? Seems like it does what I'm after, and presumably a linter would catch these errors early on? – Seb Feb 07 '13 at 15:16
  • @Seb: A linter would catch your errors without using `__slots__` too. Conclusion: use a linter instead of abusing `__slots__`. – Martijn Pieters Feb 07 '13 at 15:18
  • @MartijnPieters righto, point taken. Maybe I'll spend a bit of time figuring out how to get my linter to work often enough to be useful...! – Seb Feb 07 '13 at 15:19
  • I want to post a link to code.activestate with a better solution but I get the error links to not allowed – Dhara Feb 07 '13 at 15:20
  • http://stackoverflow.com/questions/472000/python-slots explains that using __slots__ in this way would make me a control freak or a static typing weenie. Guess I'll just stick to the linter :) – Seb Feb 07 '13 at 15:28
0

You can use the answer posted above, but for a more "pythonic" approach, try the method listed at (link to code.activestate.com)

For future reference, and until I can figure out how to link to the website, here's the code:

def frozen(set):
    """Raise an error when trying to set an undeclared name, or when calling
       from a method other than Frozen.__init__ or the __init__ method of
       a class derived from Frozen"""
    def set_attr(self,name,value):
        import sys
        if hasattr(self,name):                                  #If attribute already exists, simply set it
            set(self,name,value)
            return
        elif sys._getframe(1).f_code.co_name is '__init__':     #Allow __setattr__ calls in __init__ calls of proper object types
            for k,v in sys._getframe(1).f_locals.items():
                if k=="self" and isinstance(v, self.__class__):
                    set(self,name,value)
                    return
        raise AttributeError("You cannot add attributes to %s" % self)
    return set_attr
Dhara
  • 6,587
  • 2
  • 31
  • 46
  • That's rather clever. But as Martijn Pieters pointed out, it seems pylint actually picks the kind of errors I was trying to prevent without any extra plumbing. So maybe my question is redundant... – Seb Feb 07 '13 at 15:22