Instead of setting instance attributes within __init__

I want to make sure that instances of my class have a bunch of attributes. This is the way I’ve always done it in the past:

>>> class C(object):
... def __init__(self):
... self.a = 1
... self.b = 2
...

It gets the job done fine. But when there’s real work to be done in __init__, then I end up with a really long __init__ method with essentially two separate goals. One section does interesting stuff with the parameters passed in, and the other section creates a bunch of attributes.

So now I’m experimenting with setting properties up for my classes like this:

>>> class D(object):
... @property
... def a(self):
... if not hasattr(self, 'a'):
... self.a =1
... return self.a

I like how I’ve moved the instance variables out of __init__, so that my __init__ method can focus entirely on handling the parameters. I’m curious what problems I’m going to have. At first, I thought I would trigger some infinite loop by accessing self.a when inside the property for a, but so far, I haven’t had any problems.

So what is wrong with this approach?

Published by

matt

My name is Matt Wilson. I make websites for Kiwee to pay the bills. I live in Cleveland Heights, Ohio, with my wife and son.

  • http://just-another.net Benjamin W. Smith

    I can't speak for any technical pitfalls you may have, but I do have a concern with maintainability..

    In my opinion, __init__ is for initializing stuff, so what's wrong with initializing instance attributes there? Seems to me that your way abstracts that in the sense that it takes what would I would normally look for in __init__ and put it somewhere else.. Just a thought that I'm sure you've had already 🙂

  • Richard Jones

    From the example it looks like you just want some class attributes holding those default values…

  • Simon Hibbs

    What ben said. This way I'd have to look through the much more verbose code for every property to get a view of the default values, rather than having them all in one place in a more concise form.

    You can always break up the argument parsing bit from the default values bit with a comment.

    Simon Hibbs

  • http://paddy3118.blogspot.com Paddy3118

    Hi,
    Every property takes 5 lines instead of 1? And you were worried about length???

    You could call a separate method from init to do the setting.

    – Paddy.

  • Simon Wittber

    Have you tried to run your code, it does not, and can never work.

    >>> class D(object):
    … @property
    … def a(self):
    … if not hasattr(self, 'a'):
    … self.a =1

    >>>
    >>> d = D()
    >>> print d.a
    Traceback (most recent call last):
    File “<stdin>”, line 1, in <module>
    File “<stdin>”, line 5, in a
    AttributeError: can't set attribute
    >>>

  • http://noahgift.com Noah Gift

    Excellent question! Because we don't want Python to be like Perl 🙂 Recently I stumbled on this quote from Damian Conway in Perl Best Practices,
    “Perl's approach to object oriented is almost excessively Perlish: there are far too many ways to do it….
    There are just so many possible combinations of implementation, structure, and semantics that it's quite rare
    to find two unrelated class hierarchies that use precisely the same style of Perl OO.”

  • http://bruynooghe.blogspot.com Floris Bruynooghe

    Maybe you shouldn't try to do real work in __init__()?

    http://misko.hevery.com/code-reviewers-guide/fl

    I'm not saying this is always the best solution, but do find that when I think about this while wanting to do real work in __init__() often I find an alternative way of doing things that's rather elegant. And the line of what “real” work is can be quite fine too sometimes.

    Oh, I also agree with the comments about properties taking up so many more lines.

  • http://jackdied.blogspot.com/ Jack Diederich

    The best reason to do it in __init__ is because everyone reading your code expects it to be done in __init__. In a few cases I've done it in a reset() method that is the first thing called in __init__.

    Also, are you sure you're not getting a loop in that 'a' method? The first time you actually access the property it should loop and eventually bail with an error. [I just tried it – it quits after 999 recursive calls and raises an AttributeError]

  • http://www.mazelife.com James

    If you're really worried about length, I guess you could do something like this:

    class D(object):
    __slots__ = ('a', 'b')

    def __init__(self, attr_defaults={'a': 1,'b': 2}):
    [setattr(self, key, value) for key, value in attr_defaults.items()]

    …seems to me to retain readability, although it's not the usual way of doing things.

  • http://www.mazelife.com James

    Oops, comments erased my spaces. Hopefully more readable version:

    >>>class D(object):
    … __slots__ = ('a', 'b')
    … def __init__(self, attr_defaults={'a': 1,'b': 2}):
    … [setattr(self, key, value) for key, value in attr_defaults.items()]

  • http://evilchuck.com Chuck

    why not something like:

    class C(object):
    a = 1
    b = 2
    def __init__(self):
    # Init code goes here

  • http://linil.wordpress.com Rodrigo

    Guess you should really use the __init__ methods to initialize your attributes. If you really don't want to do that, you can use some metaprogramming though:

    class Test:
    def __getattr__(self,attr):
    self.__dict__[attr] = 1
    return self.__dict__[attr]

  • Paul Hildebrandt

    Seems like a lot of mechanism. If the goal is to reduce the size of __init__ why not use a function like
    def __init_vars(self):
    self.a=1
    self.b=2
    and call that from the __init__ function.

    In both cases I would worry about tools not understanding what is going on. Pulling the variable initialization out of the __init__ function will fool programs like pylint and doxygen.

    I would probably just create sections in my __init__ like:

    def __init(self):
    #
    # Variable
    #
    self.a=1
    self.b=2

    #
    # Object setup
    #
    do interesting stuff here

    But then again I do a lot of support programming and I like boring simple programs. They make it easier for me to understand.

  • anon

    > So what is wrong with this approach?
    Execution speed.

  • sk

    How about initializing instance variables first thing inside __init__. Then you can call descriptively named methods that each do their own “interesting thing”. Finally, one line above the class declaration can be a class decorator (PDF) that “makes sure that instances of my class have a bunch of attributes”.

  • http://wyattbaldwin.com/ Wyatt Baldwin

    I use this technique frequently, though with a different style, and usually just for something that's expensive to calculate (and can be cached):

    >>> class C(object):

    … @property
    … def a(self):
    … try:
    … self._a
    … except AttributeError:
    … # Do possibly expensive stuff here
    … self._a = <result of possibly expensive stuff>
    … return self._a

    I think this has better performance than using hasattr. Also, I wonder if having a @property and instance variable with the same name would cause problems somewhere; that's why I use `_a`.

    Note: I didn't read other comments first. Sorry for any repetition.

  • http://wyattbaldwin.com/ Wyatt Baldwin

    >>> class C(object):
    …………@property
    …………def a(self):
    …………….try:
    ………………..self._a
    …………….except AttributeError:
    ………………..# Do possibly expensive stuff here
    ………………..self._a = <result of possibly expensive stuff>
    …………….return self._a

  • Ben Finney

    One problem is it's more verbose, and rather unusual. The reader isn't going to expect what you're doing, which is always a negative when deciding whether to be clever in code 🙂

    Another problem is you've defined only a read-only property: the decorator wraps the getter, but the property has no setter (or deleter or docstring).

  • saluk

    I have the opposite opinion – __init__ should not be doing anything OTHER than setting the default attributes. When I type a = Object(), I want a to be valid. Putting too much stuff inside of __init__ can make it harder to see why something is actually failing. Sometimes though, I do delegate out to other functions from __init__ which do more work if necessary.

    The above just looks like a mess!

  • bc

    You might be interested in Traits (http://code.enthought.com/projects/traits/). Traits-based classes use declarative (static) initialisation or dynamic (on demand) initialisation like so:

    from enthought.traits.api import HasTraits, Float

    class MyClass(HasTraits):
    a = Float(1.234) #default value declared statically

    class AnotherClass(HasTraits):
    a = Float()

    def _a_default(self): #dynamic init setup using naming convention
    return 1.234

    for the later, the 'a' attribute is calculated on demand.

    Traits also does runtime type checking, notification, delegation and GUI-generation. There are also Property traits which are cacheable and handle depenencies. There are predefined traits for all the standard types and you can subclass your own.

  • bc

    Here's an indented version, in case it's not obvious how it should have read:

    class MyClass(HasTraits):
    ….a = Float(1.234) #default value declared statically
    .
    .
    class AnotherClass(HasTraits):
    ….a = Float()

    ….def _a_default(self): #dynamic init setup using naming convention
    ……..return 1.234

  • http://www.ResumeBuilderPlus.com Resumes

    The first actually works well and gets the job done – trusted and proven. As for your experiment, I can't wait to try it for myself and see how it betters the latter. Cheers for the effort Matt!

  • http://www.condomman.com/articles/condom-use/advantages-of-buying-condoms-online/ cheap condoms

    In my opinion, __init__ is for initializing stuff, so what's wrong with initializing instance attributes there?You can always break up the argument parsing bit from the default values bit with a comment.

  • http://www.condomman.com/articles/condom-use/advantages-of-buying-condoms-online/ cheap condoms

    In my opinion, __init__ is for initializing stuff, so what's wrong with initializing instance attributes there?You can always break up the argument parsing bit from the default values bit with a comment.