Proper class initialization

Collapse
This topic is closed.
X
X
 
  • Time
  • Show
Clear All
new posts
  • Christoph Zwerschke

    #1

    Proper class initialization

    Usually, you initialize class variables like that:

    class A:
    sum = 45

    But what is the proper way to initialize class variables if they are the
    result of some computation or processing as in the following silly
    example (representative for more:

    class A:
    sum = 0
    for i in range(10):
    sum += i

    The problem is that this makes any auxiliary variables (like "i" in this
    silly example) also class variables, which is not desired.

    Of course, I could call a function external to the class

    def calc_sum(n):
    ...

    class A:
    sum = calc_sum(10)

    But I wonder whether it is possible to put all this init code into one
    class initialization method, something like that:

    class A:

    @classmethod
    def init_class(self ):
    sum = 0
    for i in range(10):
    sum += i
    self.sum = sum

    init_class()

    However, this does not work, I get
    TypeError: 'classmethod' object is not callable

    Is there another way to put an initialization method for the class A
    somewhere *inside* the class A?

    -- Christoph
  • Jack Diederich

    #2
    Re: Proper class initialization

    On Wed, Mar 01, 2006 at 09:25:36PM +0100, Christoph Zwerschke wrote:[color=blue]
    > Usually, you initialize class variables like that:
    >
    > class A:
    > sum = 45
    >
    > But what is the proper way to initialize class variables if they are the
    > result of some computation or processing as in the following silly
    > example (representative for more:
    >
    > class A:
    > sum = 0
    > for i in range(10):
    > sum += i
    >
    > The problem is that this makes any auxiliary variables (like "i" in this
    > silly example) also class variables, which is not desired.
    >
    > Of course, I could call a function external to the class
    >
    > def calc_sum(n):
    > ...
    >
    > class A:
    > sum = calc_sum(10)
    >
    > But I wonder whether it is possible to put all this init code into one
    > class initialization method, something like that:[/color]

    Yes, it is called a meta class.
    [color=blue]
    > class A:
    >
    > @classmethod
    > def init_class(self ):
    > sum = 0
    > for i in range(10):
    > sum += i
    > self.sum = sum
    >
    > init_class()
    >
    > However, this does not work[/color]

    What we normally think of as an instance is an instance of a class.
    Classes are actually instances of metaclasses. What you are looking for
    is __init__, but not the __init__ defined after 'class A...' you want
    the __init__ that is called when you type 'class A....'

    Python 2.4.1 (#2, Mar 30 2005, 21:51:10)
    [GCC 3.3.5 (Debian 1:3.3.5-8ubuntu2)] on linux2
    Type "help", "copyright" , "credits" or "license" for more information.[color=blue][color=green][color=darkred]
    >>> class MyMeta(type):[/color][/color][/color]
    .... def __init__(cls, *ignored):
    .... cls.sum = 0
    .... for (i) in range(10):
    .... cls.sum += i
    ....[color=blue][color=green][color=darkred]
    >>> class A(object):[/color][/color][/color]
    .... __metaclass__ = MyMeta
    ....[color=blue][color=green][color=darkred]
    >>> print A.sum[/color][/color][/color]
    45[color=blue][color=green][color=darkred]
    >>> print A.i[/color][/color][/color]
    Traceback (most recent call last):
    File "<stdin>", line 1, in ?
    AttributeError: type object 'A' has no attribute 'i'[color=blue][color=green][color=darkred]
    >>>[/color][/color][/color]

    For something as simple as this example it is easier and cleaner
    to move the loop into a function and do the one-liner assignment.
    Because the metaclass can do _anything_ to the class the reader
    is obliged to go read its code. The simple assignment from a
    function obviously has no side effects.

    Hope that helps,

    -jackdied

    Comment

    • Larry Bates

      #3
      Re: Proper class initialization

      Christoph Zwerschke wrote:[color=blue]
      > Usually, you initialize class variables like that:
      >
      > class A:
      > sum = 45
      >
      > But what is the proper way to initialize class variables if they are the
      > result of some computation or processing as in the following silly
      > example (representative for more:
      >
      > class A:
      > sum = 0
      > for i in range(10):
      > sum += i
      >
      > The problem is that this makes any auxiliary variables (like "i" in this
      > silly example) also class variables, which is not desired.
      >
      > Of course, I could call a function external to the class
      >
      > def calc_sum(n):
      > ...
      >
      > class A:
      > sum = calc_sum(10)
      >
      > But I wonder whether it is possible to put all this init code into one
      > class initialization method, something like that:
      >
      > class A:
      >
      > @classmethod
      > def init_class(self ):
      > sum = 0
      > for i in range(10):
      > sum += i
      > self.sum = sum
      >
      > init_class()
      >
      > However, this does not work, I get
      > TypeError: 'classmethod' object is not callable
      >
      > Is there another way to put an initialization method for the class A
      > somewhere *inside* the class A?
      >
      > -- Christoph[/color]

      Although I've never had the need for something like this,
      this works:

      class A:
      sum=0
      for i in range(10):
      sum+=i
      del i

      or moving the initialization into __init__ method isn't
      terribly inefficient unless are are creating LOTS of
      instances of the same class.

      class A:
      def __init__(self):
      self.sum=0
      for i in range(10):
      self.sum+=i


      or you can do the do it before you instantiate the class

      class A:
      def __init__(self, initialvalue=No ne):
      if initialvalue is not None: self.sum=initia lvalue
      else: self.sum=0


      for i in range(10):
      sum+=i

      b=A(sum)
      c=A(sum)

      -Larry Bates

      Comment

      • Christoph Zwerschke

        #4
        Re: Proper class initialization

        Jack Diederich wrote:[color=blue]
        > ... __metaclass__ = MyMeta[/color]

        Thanks. I was not aware of the __metaclass__ attribute. Still a bit
        complicated and as you said, difficult to read, as the other workarounds
        already proposed. Anyway, this is probably not needed so often.

        -- Christoph

        Comment

        • Steven Bethard

          #5
          Re: Proper class initialization

          Christoph Zwerschke wrote:[color=blue]
          > But I wonder whether it is possible to put all this init code into one
          > class initialization method, something like that:
          >
          > class A:
          >
          > @classmethod
          > def init_class(self ):
          > sum = 0
          > for i in range(10):
          > sum += i
          > self.sum = sum
          >
          > init_class()[/color]

          I don't run into this often, but when I do, I usually go Jack
          Diederich's route::

          class A(object):
          class __metaclass__(t ype):
          def __init__(cls, name, bases, classdict):
          cls.sum = sum(xrange(10))

          But you can also go something more akin to your route::

          class A(object):
          def _get_sum():
          return sum(xrange(10))
          sum = _get_sum()

          Note that you don't need to declare _get_sum() as a classmethod, because
          it's not actually a classmethod -- it's getting used before the class
          yet exists. Just write it as a normal function and use it as such --
          that is, no ``self`` or ``cls`` parameter.

          STeVe

          Comment

          • Leif K-Brooks

            #6
            Re: Proper class initialization

            Steven Bethard wrote:[color=blue]
            > class A(object):
            > def _get_sum():
            > return sum(xrange(10))
            > sum = _get_sum()[/color]

            What's wrong with sum = sum(xrange(10)) ?

            Comment

            • Steven Bethard

              #7
              Re: Proper class initialization

              Leif K-Brooks wrote:[color=blue]
              > Steven Bethard wrote:[color=green]
              >> class A(object):
              >> def _get_sum():
              >> return sum(xrange(10))
              >> sum = _get_sum()[/color]
              >
              > What's wrong with sum = sum(xrange(10)) ?[/color]

              Nothing, except that it probably doesn't answer the OP's question. The
              OP presented a "silly example":

              class A:
              sum = 0
              for i in range(10):
              sum += i

              I assume the intention was to indicate that the initialization required
              multiple statements. I just couldn't bring myself to write that
              horrible for-loop when the sum() function is builtin. ;)

              STeVe

              Comment

              • Christoph Zwerschke

                #8
                Re: Proper class initialization

                Steven Bethard wrote:[color=blue]
                > I assume the intention was to indicate that the initialization required
                > multiple statements. I just couldn't bring myself to write that
                > horrible for-loop when the sum() function is builtin. ;)[/color]

                Yes, this was just dummy code standing for something that really
                requires multiple statements and auxiliary variables.

                -- Christoph

                Comment

                • Christoph Zwerschke

                  #9
                  Re: Proper class initialization

                  Steven Bethard wrote:[color=blue]
                  > I don't run into this often, but when I do, I usually go Jack
                  > Diederich's route::
                  >
                  > class A(object):
                  > class __metaclass__(t ype):
                  > def __init__(cls, name, bases, classdict):
                  > cls.sum = sum(xrange(10))[/color]

                  Good idea, that is really nice and readable. Now all the init code is
                  defined inline at the top of the class definition.
                  [color=blue]
                  > But you can also go something more akin to your route::
                  >
                  > class A(object):
                  > def _get_sum():
                  > return sum(xrange(10))
                  > sum = _get_sum()
                  >
                  > Note that you don't need to declare _get_sum() as a classmethod, because
                  > it's not actually a classmethod -- it's getting used before the class
                  > yet exists. Just write it as a normal function and use it as such --
                  > that is, no ``self`` or ``cls`` parameter.[/color]

                  Often it's so simple ;-) But the disadvantage is then that you cannot
                  set the class variables directly inside that function. So if you have to
                  initialize several of them, you need to define several functions, or
                  return them as a tuple and it gets a bit ugly. In this case, the first
                  solution may be better.

                  -- Christoph

                  Comment

                  • Kent Johnson

                    #10
                    Re: Proper class initialization

                    Steven Bethard wrote:[color=blue]
                    > I don't run into this often, but when I do, I usually go Jack
                    > Diederich's route::
                    >
                    > class A(object):
                    > class __metaclass__(t ype):
                    > def __init__(cls, name, bases, classdict):
                    > cls.sum = sum(xrange(10))[/color]

                    I think you should call the superclass __init__ as well:

                    class __metaclass__(t ype):
                    def __init__(cls, name, bases, classdict):
                    super(__metacla ss__, cls).__init__(n ame, bases, classdict)
                    cls.sum = sum(xrange(10))

                    Kent

                    Comment

                    • gry@ll.mit.edu

                      #11
                      Re: Proper class initialization

                      Christoph Zwerschke wrote:[color=blue]
                      > Usually, you initialize class variables like that:
                      >
                      > class A:
                      > sum = 45
                      >
                      > But what is the proper way to initialize class variables if they are the
                      > result of some computation or processing as in the following silly
                      > example (representative for more:
                      >
                      > class A:
                      > sum = 0
                      > for i in range(10):
                      > sum += i
                      >
                      > The problem is that this makes any auxiliary variables (like "i" in this
                      > silly example) also class variables, which is not desired.
                      >
                      > Of course, I could call a function external to the class
                      >
                      > def calc_sum(n):
                      > ...
                      >
                      > class A:
                      > sum = calc_sum(10)
                      >
                      > But I wonder whether it is possible to put all this init code into one
                      > class initialization method, something like that:
                      >
                      > class A:
                      >
                      > @classmethod
                      > def init_class(self ):
                      > sum = 0
                      > for i in range(10):
                      > sum += i
                      > self.sum = sum
                      >
                      > init_class()
                      >
                      > However, this does not work, I get
                      > TypeError: 'classmethod' object is not callable
                      >
                      > Is there another way to put an initialization method for the class A
                      > somewhere *inside* the class A?[/color]
                      Hmm, the meta-class hacks mentioned are cool, but for this simple a
                      case how about just:

                      class A:
                      def __init__(self):
                      self.__class__. sum = self.calculate_ sum()
                      def calculate_sum(s elf):
                      do_stuff
                      return sum_value

                      Instead of __class__ you could say:
                      A.sum = self.calculate_ sum()
                      but that fails if you rename the class. I believe either works fine
                      in case of classes derived from A.

                      -- George Young

                      Comment

                      • Steven Bethard

                        #12
                        Re: Proper class initialization

                        Kent Johnson wrote:[color=blue]
                        > Steven Bethard wrote:[color=green]
                        >> I don't run into this often, but when I do, I usually go Jack
                        >> Diederich's route::
                        >>
                        >> class A(object):
                        >> class __metaclass__(t ype):
                        >> def __init__(cls, name, bases, classdict):
                        >> cls.sum = sum(xrange(10))[/color]
                        >
                        > I think you should call the superclass __init__ as well:
                        >
                        > class __metaclass__(t ype):
                        > def __init__(cls, name, bases, classdict):
                        > super(__metacla ss__, cls).__init__(n ame, bases, classdict)
                        > cls.sum = sum(xrange(10))[/color]

                        Yes, thanks for the catch.

                        I'd like to use super there, but I think you'll find that it doesn't
                        work because of the order in which A and __metaclass__ get created:
                        [color=blue][color=green][color=darkred]
                        >>> class A(object):[/color][/color][/color]
                        .... class __metaclass__(t ype):
                        .... def __init__(cls, name, bases, cdict):
                        .... super(__metacla ss__, cls).__init__(n ame, bases, cdict)
                        .... cls.sum = sum(xrange(10))
                        ....
                        Traceback (most recent call last):
                        File "<interacti ve input>", line 1, in ?
                        File "<interacti ve input>", line 4, in __init__
                        NameError: global name '__metaclass__' is not defined

                        [color=blue][color=green][color=darkred]
                        >>> class A(object):[/color][/color][/color]
                        .... class __metaclass__(t ype):
                        .... def __init__(cls, name, bases, classdict):
                        .... super(A.__metac lass__, cls).__init__(n ame, bases, classdict)
                        .... cls.sum = sum(xrange(10))
                        ....
                        Traceback (most recent call last):
                        File "<interacti ve input>", line 1, in ?
                        File "<interacti ve input>", line 4, in __init__
                        NameError: global name 'A' is not defined


                        I played around with a few solutions to this problem, but it seems like
                        the cleanest one is just to avoid super:
                        [color=blue][color=green][color=darkred]
                        >>> class A(object):[/color][/color][/color]
                        .... class __metaclass__(t ype):
                        .... def __init__(cls, name, bases, cldict):
                        .... type.__init__(c ls, name, bases, cldict)
                        .... cls.sum = sum(xrange(10))
                        ....

                        Of course, this means you can't use some sorts of multiple inheritance
                        with A.__metaclass__ ...

                        STeVe

                        P.S. Here's the best way I found that still uses super:
                        [color=blue][color=green][color=darkred]
                        >>> class A(object):[/color][/color][/color]
                        .... class __metaclass__(t ype):
                        .... def __init__(cls, name, bases, cldict):
                        .... try:
                        .... meta = A.__metaclass__
                        .... except NameError:
                        .... meta = cldict['__metaclass__']
                        .... super(meta, cls).__init__(n ame, bases, cldict)
                        .... cls.sum = sum(xrange(10))
                        ....

                        Comment

                        • Christoph Zwerschke

                          #13
                          Re: Proper class initialization

                          gry@ll.mit.edu schrieb:[color=blue]
                          > Hmm, the meta-class hacks mentioned are cool, but for this simple a
                          > case how about just:
                          >
                          > class A:
                          > def __init__(self):
                          > self.__class__. sum = self.calculate_ sum()
                          > def calculate_sum(s elf):
                          > do_stuff
                          > return sum_value[/color]

                          If you do it like that, Steven's second suggestion was better:

                          class A:
                          def calculate_sum() :
                          do_stuff
                          return sum_value
                          sum = calculate_sum()

                          That's not only easier simpler, it also avoids calling calculate_sum()
                          every time you create an instance.

                          -- Christoph

                          Comment

                          Working...