Search code examples
pythonattributesclass-designmodule

Python Class vs. Module Attributes


I'm interested in hearing some discussion about class attributes in Python. For example, what is a good use case for class attributes? For the most part, I can not come up with a case where a class attribute is preferable to using a module level attribute. If this is true, then why have them around?

The problem I have with them, is that it is almost too easy to clobber a class attribute value by mistake, and then your "global" value has turned into a local instance attribute.

Feel free to comment on how you would handle the following situations:

  1. Constant values used by a class and/or sub-classes. This may include "magic number" dictionary keys or list indexes that will never change, but possible need one-time initialization.
  2. Default class attribute, that in rare occasions updated for a special instance of the class.
  3. Global data structure used to represent an internal state of a class shared between all instances.
  4. A class that initializes a number of default attributes, not influenced by constructor arguments.

Some Related Posts:
Difference Between Class and Instance Attributes


Solution

  • #4: I never use class attributes to initialize default instance attributes (the ones you normally put in __init__). For example:

    class Obj(object):
        def __init__(self):
            self.users = 0
    

    and never:

    class Obj(object):
        users = 0
    

    Why? Because it's inconsistent: it doesn't do what you want when you assign anything but an invariant object:

    class Obj(object):
        users = []
    

    causes the users list to be shared across all objects, which in this case isn't wanted. It's confusing to split these into class attributes and assignments in __init__ depending on their type, so I always put them all in __init__, which I find clearer anyway.


    As for the rest, I generally put class-specific values inside the class. This isn't so much because globals are "evil"--they're not so big a deal as in some languages, because they're still scoped to the module, unless the module itself is too big--but if external code wants to access them, it's handy to have all of the relevant values in one place. For example, in module.py:

    class Obj(object):
        class Exception(Exception): pass
        ...
    

    and then:

    from module import Obj
    
    try:
        o = Obj()
        o.go()
    except o.Exception:
        print "error"
    

    Aside from allowing subclasses to change the value (which isn't always wanted anyway), it means I don't have to laboriously import exception names and a bunch of other stuff needed to use Obj. "from module import Obj, ObjException, ..." gets tiresome quickly.