OOP / language design question

Collapse
This topic is closed.
X
X
 
  • Time
  • Show
Clear All
new posts
  • Carl Banks

    #16
    Re: OOP / language design question

    Duncan Booth wrote:[color=blue]
    > In other words, the object is constructed in Python before any __init__ is
    > called, but in C++ it isn't constructed until after all the base class
    > constructors have returned.[/color]

    That's true. Good point.


    Carl Banks

    Comment

    • bruno at modulix

      #17
      Re: OOP / language design question

      Duncan Booth wrote:[color=blue]
      > bruno at modulix wrote:
      >
      >[color=green]
      >>Duncan Booth wrote:
      >>(snip)
      >>[color=darkred]
      >>>Usually though, if a subclass doesn't immediately call the base class
      >>>constructo rs as the first thing it does in __init__ it indicates poor
      >>>code and should be refactored.[/color]
      >>
      >>Not necessarily. It's a common case to have some computations to
      >>do/some attributes to set in the derived class's __init__ before
      >>calling the superclass's.
      >>[/color]
      >
      >
      > I did only say 'usually'. Can you actually think of any good examples where
      > you have to set a derived attribute before you can call the base class
      > constructor?[/color]

      class Base(object):
      def __init__(self, arg1):
      self.attr1 = arg1
      self.dothis()

      def dothis(self):
      return self.attr1

      class Derived(Base):
      def __init__(self, arg1, arg2=0):
      self.attr2 = arg2
      Base.__init__(s elf, arg1)

      def dothis(self):
      return self.attr1 + self.attr2

      (snip)
      [color=blue]
      > Perhaps if the base __init__ calls an overridden
      > method, but at that point it sounds to me like something wants refactoring.[/color]

      Why so ? This is a well-known pattern (template method). I don't see
      what's wrong with it.
      [color=blue]
      > I can think that you might have to do some computations to calculate
      > parameters for the base __init__, but that is a separate issue.[/color]


      --
      bruno desthuilliers
      python -c "print '@'.join(['.'.join([w[::-1] for w in p.split('.')]) for
      p in 'onurb@xiludom. gro'.split('@')])"

      Comment

      • Duncan Booth

        #18
        Re: OOP / language design question

        bruno at modulix wrote:
        [color=blue]
        > class Base(object):
        > def __init__(self, arg1):
        > self.attr1 = arg1
        > self.dothis()
        >
        > def dothis(self):
        > return self.attr1
        >
        > class Derived(Base):
        > def __init__(self, arg1, arg2=0):
        > self.attr2 = arg2
        > Base.__init__(s elf, arg1)
        >
        > def dothis(self):
        > return self.attr1 + self.attr2
        >
        > (snip)
        >[color=green]
        >> Perhaps if the base __init__ calls an overridden
        >> method, but at that point it sounds to me like something wants
        >> refactoring.[/color]
        >
        > Why so ? This is a well-known pattern (template method). I don't see
        > what's wrong with it.[/color]

        Apart from the fact that you can delete the method 'dothis' from both
        classes with no effect on the code?

        Actually, this is quite an interesting example becaue it wouldn't work in
        C++: if you tried the same trick the call to dothis from the base
        constructor (even assuming it is virtual) would actually call Base.dothis.

        I think you have demonstrated that you can do some things in Python which
        you simply cannot do in static languages like C++: assigning to derived
        attributes before calling a base initialiser, and calling a virtual method
        from a base initialiser. I'm not arguing about that. What I don't have
        though is an example of code where doing this would actually be a good
        thing.

        What I think I'm trying to get at is that I believe that most situations
        where someone actually tries to do something in the base initialiser which
        requires calling a virtual method are probably also cases where the
        initialiser is doing too much: i.e. separating the
        construction/initialisation from actually doing something is usually a good
        idea.

        Comment

        • Bruno Desthuilliers

          #19
          Re: OOP / language design question

          Duncan Booth a écrit :[color=blue]
          > bruno at modulix wrote:
          >
          >[color=green]
          >>class Base(object):
          >> def __init__(self, arg1):
          >> self.attr1 = arg1
          >> self.dothis()
          >>
          >> def dothis(self):
          >> return self.attr1
          >>
          >>class Derived(Base):
          >> def __init__(self, arg1, arg2=0):
          >> self.attr2 = arg2
          >> Base.__init__(s elf, arg1)
          >>
          >> def dothis(self):
          >> return self.attr1 + self.attr2
          >>
          >>(snip)
          >>
          >>[color=darkred]
          >>>Perhaps if the base __init__ calls an overridden
          >>>method, but at that point it sounds to me like something wants
          >>>refactorin g.[/color]
          >>
          >>Why so ? This is a well-known pattern (template method). I don't see
          >>what's wrong with it.[/color]
          >
          >
          > Apart from the fact that you can delete the method 'dothis' from both
          > classes with no effect on the code?[/color]

          Mmmm... Oh, I see. Agreed, this is not a very good example.
          [color=blue][color=green]
          >>class Base(object):
          >> def __init__(self, arg1):
          >> self.attr1 = arg1
          >> self.attr42 = self.dothis()[/color][/color]

          Is that better ?-)

          Ok, let's be serious now...
          [color=blue]
          > Actually, this is quite an interesting example becaue it wouldn't work in
          > C++: if you tried the same trick the call to dothis from the base
          > constructor (even assuming it is virtual) would actually call Base.dothis.[/color]
          [color=blue]
          > I think you have demonstrated that you can do some things in Python which
          > you simply cannot do in static languages like C++: assigning to derived
          > attributes before calling a base initialiser, and calling a virtual method
          > from a base initialiser. I'm not arguing about that. What I don't have
          > though is an example of code where doing this would actually be a good
          > thing.[/color]

          I don't have any concrete example at hand, but I can tell you I've done
          such things often enough.
          [color=blue]
          > What I think I'm trying to get at is that I believe that most situations
          > where someone actually tries to do something in the base initialiser which
          > requires calling a virtual method[/color]

          I'm afraid I fail to see what's so special about 'virtual' methods - and
          FWIW, since all methods in Python are virtual, if you don't want to call
          virtual methods in the initializer, you won't get very far !-)
          [color=blue]
          > are probably also cases where the
          > initialiser is doing too much: i.e. separating the
          > construction/initialisation from actually doing something is usually a good
          > idea.[/color]

          What if some of the things one have to do at initialization is also used
          elsewhere ? You would not duplicate code, would you ?

          Comment

          • Alex Martelli

            #20
            Re: OOP / language design question

            Duncan Booth <duncan.booth@i nvalid.invalid> wrote:
            ...[color=blue]
            > Actually, this is quite an interesting example becaue it wouldn't work in
            > C++: if you tried the same trick the call to dothis from the base
            > constructor (even assuming it is virtual) would actually call Base.dothis.[/color]

            Yep.
            [color=blue]
            > I think you have demonstrated that you can do some things in Python which
            > you simply cannot do in static languages like C++: assigning to derived
            > attributes before calling a base initialiser, and calling a virtual method
            > from a base initialiser. I'm not arguing about that. What I don't have
            > though is an example of code where doing this would actually be a good
            > thing.[/color]

            A recognized idiom/pattern in C++ or Java is known as "two-phase
            construction" (yes, there's a two-phase destruction counterpart): have
            minimal, near-empty constructors, then a separate virtual Init method
            which does the actual construction work (often with a framework or at
            least a factory to ensure that Init gets in fact called).

            Each use case of this idiom/pattern relies on virtual methods (most
            generally in a Template Method design pattern) at initialization.

            E.g.: build a composite window by iteratively building subwindows
            (including decorators) as enumerated by a virtual method. Initialize a
            database-connection object by delegating some parts (such as connection,
            local or over the net, and authentication, etc etc) to virtual methods.
            And so on, and so forth.

            [color=blue]
            > What I think I'm trying to get at is that I believe that most situations
            > where someone actually tries to do something in the base initialiser which
            > requires calling a virtual method are probably also cases where the
            > initialiser is doing too much: i.e. separating the
            > construction/initialisation from actually doing something is usually a good
            > idea.[/color]

            But why should that be? Template Method is perhaps the MOST generally
            useful design pattern -- why would it be any less useful in
            initialization than elsewhere?!


            Alex

            Comment

            • Duncan Booth

              #21
              Re: OOP / language design question

              Alex Martelli wrote:
              [color=blue][color=green]
              >> What I think I'm trying to get at is that I believe that most
              >> situations where someone actually tries to do something in the base
              >> initialiser which requires calling a virtual method are probably also
              >> cases where the initialiser is doing too much: i.e. separating the
              >> construction/initialisation from actually doing something is usually
              >> a good idea.[/color]
              >
              > But why should that be? Template Method is perhaps the MOST generally
              > useful design pattern -- why would it be any less useful in
              > initialization than elsewhere?!
              >[/color]
              Because it is error prone?

              Any method which is called from the constructor/initialiser has to operate
              correctly on an object which at that point is not fully
              constructed/initialised. So instead of having to write a method on a Foo
              object, your template method has to operate on a partial Foo. The danger is
              that you haven't clearly defined the partial Foo interface sufficiently and
              the method tries to use other parts of the object which haven't yet been
              set up. That situation gets worse when you have a class hierarchy as the
              subclass needs to know that it has to do complete its own initialisation
              before constructing the base class instead of afterwards, and if you are
              going to document that requirement, why not do it properly and split the
              construction in two?

              That's why I would go for the 2-phase construction: after the first phase
              you have an object which is fully initialised, just not yet
              used/connected/running. For example httplib.HTTPCon nection does this: you
              construct the object with a host and port, but the actual connection is
              triggered by a separate object.
              I would suggest your example of a database connection belongs in that
              category: it should have an initial unconnected idle state and a separate
              connection.

              I think your example of a composite window building subwindows is the sort
              of use case I was asking for: it does sound tempting to construct the
              window and all its subwindows together. I'm happy to concede on that one.

              Comment

              • bruno at modulix

                #22
                Re: OOP / language design question

                Duncan Booth wrote:[color=blue]
                > Alex Martelli wrote:
                >
                >[color=green][color=darkred]
                >>>What I think I'm trying to get at is that I believe that most
                >>>situations where someone actually tries to do something in the base
                >>>initialise r which requires calling a virtual method are probably also
                >>>cases where the initialiser is doing too much: i.e. separating the
                >>>constructi on/initialisation from actually doing something is usually
                >>>a good idea.[/color]
                >>
                >>But why should that be? Template Method is perhaps the MOST generally
                >>useful design pattern -- why would it be any less useful in
                >>initializatio n than elsewhere?!
                >>[/color]
                >
                > Because it is error prone?[/color]

                Programming *is* error prone.
                [color=blue]
                > Any method which is called from the constructor/initialiser has to operate
                > correctly[/color]

                any method has to operate correctly anyway !-)
                [color=blue]
                > on an object which at that point is not fully
                > constructed/initialised.[/color]

                In Python, when the __init__ method is called, the object is at least
                fully constructed.
                [color=blue]
                > So instead of having to write a method on a Foo
                > object, your template method has to operate on a partial Foo. The danger is
                > that you haven't clearly defined the partial Foo interface sufficiently and
                > the method tries to use other parts of the object which haven't yet been
                > set up.[/color]

                If so, the worse thing that can happen is an exception - and you'll
                surely spot the problem really soon.
                [color=blue]
                > That situation gets worse when you have a class hierarchy as the
                > subclass needs to know that it has to do complete its own initialisation
                > before constructing the base class instead of afterwards, and if you are
                > going to document that requirement, why not do it properly and split the
                > construction in two?[/color]

                It's *already* split : __new__ construct the object, __init__ initialize it.
                [color=blue]
                > That's why I would go for the 2-phase construction:[/color]

                But that's already what you have.
                [color=blue]
                > after the first phase
                > you have an object which is fully initialised, just not yet
                > used/connected/running. For example httplib.HTTPCon nection does this: you
                > construct the object with a host and port, but the actual connection is
                > triggered by a separate object.[/color]

                If you look at file objects, they do try and open the file at init time.
                Is a net or db connection that different ?

                (snip)

                --
                bruno desthuilliers
                python -c "print '@'.join(['.'.join([w[::-1] for w in p.split('.')]) for
                p in 'onurb@xiludom. gro'.split('@')])"

                Comment

                • Carl Banks

                  #23
                  Re: OOP / language design question


                  Duncan Booth wrote:[color=blue]
                  > Alex Martelli wrote:
                  >[color=green][color=darkred]
                  > >> What I think I'm trying to get at is that I believe that most
                  > >> situations where someone actually tries to do something in the base
                  > >> initialiser which requires calling a virtual method are probably also
                  > >> cases where the initialiser is doing too much: i.e. separating the
                  > >> construction/initialisation from actually doing something is usually
                  > >> a good idea.[/color]
                  > >
                  > > But why should that be? Template Method is perhaps the MOST generally
                  > > useful design pattern -- why would it be any less useful in
                  > > initialization than elsewhere?!
                  > >[/color]
                  > Because it is error prone?
                  >
                  > Any method which is called from the constructor/initialiser has to operate
                  > correctly on an object which at that point is not fully
                  > constructed/initialised. So instead of having to write a method on a Foo
                  > object, your template method has to operate on a partial Foo. The danger is
                  > that you haven't clearly defined the partial Foo interface sufficiently and
                  > the method tries to use other parts of the object which haven't yet been
                  > set up.[/color]

                  In Python, if you try to use an uninitialized member you get an
                  AttributeError; I really don't see too much inherent danger here. If
                  you make a mistake, the language tells you and you fix it. C++ and
                  Java are worse since accessing uninitialized variables is a silent
                  mistake, so it makes sense to avoid that kind thing in those languages.

                  Carl Banks

                  Comment

                  • Duncan Booth

                    #24
                    Re: OOP / language design question

                    bruno at modulix wrote:
                    [color=blue]
                    > It's *already* split : __new__ construct the object, __init__
                    > initialize it.
                    >[color=green]
                    >> That's why I would go for the 2-phase construction:[/color]
                    >
                    > But that's already what you have.[/color]

                    Very good point.
                    [color=blue][color=green]
                    >> after the first phase
                    >> you have an object which is fully initialised, just not yet
                    >> used/connected/running. For example httplib.HTTPCon nection does this:
                    >> you construct the object with a host and port, but the actual
                    >> connection is triggered by a separate object.[/color]
                    >
                    > If you look at file objects, they do try and open the file at init
                    > time. Is a net or db connection that different ?[/color]

                    Well, yes, since the whole point is that we are discussing overriding
                    methods and I bet you haven't subclassed Python file objects recently.

                    For network or database connections you do want to supply your own
                    handlers for things like authentication.

                    Comment

                    • bruno at modulix

                      #25
                      Re: OOP / language design question

                      Duncan Booth wrote:[color=blue]
                      > bruno at modulix wrote:
                      >
                      >[color=green]
                      >>It's *already* split : __new__ construct the object, __init__
                      >>initialize it.
                      >>[color=darkred]
                      >>>That's why I would go for the 2-phase construction:[/color]
                      >>
                      >>But that's already what you have.[/color]
                      >
                      > Very good point.
                      >
                      >[color=green][color=darkred]
                      >>>after the first phase
                      >>>you have an object which is fully initialised, just not yet
                      >>>used/connected/running. For example httplib.HTTPCon nection does this:
                      >>>you construct the object with a host and port, but the actual
                      >>>connection is triggered by a separate object.[/color]
                      >>
                      >>If you look at file objects, they do try and open the file at init
                      >>time. Is a net or db connection that different ?[/color]
                      >
                      >
                      > Well, yes, since the whole point is that we are discussing overriding
                      > methods and I bet you haven't subclassed Python file objects recently.[/color]

                      And you win !-)

                      Anyway, I didn't suggest that opening a connection to whatever should be
                      done in the __init__ - I just wanted to point that acquiring a resource
                      in the initializer (and freeing it in the finalizer) can sometimes be
                      perfectly sensible.

                      wrt/ initializer as a template method, I still fail to see why this
                      should be a problem. The fact that one should avoid doing anything else
                      than initialization in the initializer is just plain old common sense
                      IMHO - the use of calls to other methods that can possibly be overriden
                      in a subclass is orthogonal. And if the guy writing the subclass do
                      stupid things when overridding these methods, well, too bad for him -
                      but as the author of the base class, that's definitively not my problem
                      (given proper documentation of course)

                      Trying to protect stupid programmers from doing stupid things is a total
                      waste of time anyway.

                      --
                      bruno desthuilliers
                      python -c "print '@'.join(['.'.join([w[::-1] for w in p.split('.')]) for
                      p in 'onurb@xiludom. gro'.split('@')])"

                      Comment

                      • Brian van den Broek

                        #26
                        Re: OOP / language design question

                        Bruno Desthuilliers said unto the world upon 25/04/06 06:52 PM:[color=blue]
                        > Duncan Booth a écrit :
                        >[color=green]
                        >>bruno at modulix wrote:
                        >>
                        >>
                        >>[color=darkred]
                        >>>class Base(object):
                        >>> def __init__(self, arg1):
                        >>> self.attr1 = arg1
                        >>> self.dothis()
                        >>>
                        >>> def dothis(self):
                        >>> return self.attr1
                        >>>
                        >>>class Derived(Base):
                        >>> def __init__(self, arg1, arg2=0):
                        >>> self.attr2 = arg2
                        >>> Base.__init__(s elf, arg1)
                        >>>
                        >>> def dothis(self):
                        >>> return self.attr1 + self.attr2
                        >>>
                        >>>(snip)
                        >>>
                        >>>
                        >>>
                        >>>>Perhaps if the base __init__ calls an overridden
                        >>>>method, but at that point it sounds to me like something wants
                        >>>>refactoring .
                        >>>
                        >>>Why so ? This is a well-known pattern (template method). I don't see
                        >>>what's wrong with it.[/color]
                        >>
                        >>
                        >>Apart from the fact that you can delete the method 'dothis' from both
                        >>classes with no effect on the code?[/color]
                        >
                        >
                        > Mmmm... Oh, I see. Agreed, this is not a very good example.[/color]

                        <snip>

                        This hobbyist isn't seeing Duncan's point. Wouldn't deleting the
                        dothis method from both classes lead to an AttributeError as
                        Base.__init__ calls self.dothis()?

                        Is the point that one could refactor out the self.dothis() from the
                        __init__? Or something else altogether? (I assume it can't be that
                        dothis isn't doing real work as it is in the context of a toy example.)

                        Enlightenment gratefully received.

                        Best to all,

                        Brian vdB

                        Comment

                        • bruno at modulix

                          #27
                          Re: OOP / language design question

                          Brian van den Broek wrote:[color=blue]
                          > Bruno Desthuilliers said unto the world upon 25/04/06 06:52 PM:
                          >[color=green]
                          >> Duncan Booth a écrit :
                          >>[/color][/color]
                          (snip)[color=blue][color=green][color=darkred]
                          >>> Apart from the fact that you can delete the method 'dothis' from both
                          >>> classes with no effect on the code?[/color]
                          >>
                          >> Mmmm... Oh, I see. Agreed, this is not a very good example.[/color]
                          >
                          > <snip>
                          >
                          > This hobbyist isn't seeing Duncan's point. Wouldn't deleting the dothis
                          > method from both classes lead to an AttributeError as Base.__init__
                          > calls self.dothis()?[/color]

                          Yes, of course. But Duncan (implicitely) meant "deleting the method
                          *and* the calls to the method".

                          The point is that dothis() returns a value (that is not used), and
                          doesn't modify the state of self.

                          Or at least, this what *I* understood.
                          [color=blue]
                          > Is the point that one could refactor out the self.dothis() from the
                          > __init__? Or something else altogether? (I assume it can't be that
                          > dothis isn't doing real work as it is in the context of a toy example.)[/color]

                          Seems like you are assuming too much !-)


                          --
                          bruno desthuilliers
                          python -c "print '@'.join(['.'.join([w[::-1] for w in p.split('.')]) for
                          p in 'onurb@xiludom. gro'.split('@')])"

                          Comment

                          • Lawrence D'Oliveiro

                            #28
                            Re: OOP / language design question

                            In article <Xns97B092B9B65 12duncanbooth@1 27.0.0.1>,
                            Duncan Booth <duncan.booth@i nvalid.invalid> wrote:
                            [color=blue]
                            >Carl Banks wrote:
                            >[color=green]
                            >> You know, Python's __init__ has almost the same semantics as C++
                            >> constructors (they both initialize something that's already been
                            >> allocated in memory, and neither can return a substitute object).[/color]
                            >
                            >There is a significant difference: imagine B is a base type and C a
                            >subclass of B:
                            >
                            >When you create an object of type C in Python, while B.__init__ is
                            >executing self is an object of type C (albeit without all the attributes
                            >you expect on your C).
                            >
                            >In C++ when the B() constructor is executing the object is an object of
                            >type B. It doesn't become a C object until the C() constructor is
                            >executing.
                            >
                            >In other words, the object is constructed in Python before any __init__ is
                            >called, but in C++ it isn't constructed until after all the base class
                            >constructors have returned.[/color]

                            But if "constructi on" is what a constructor does, then you're wrong.

                            Comment

                            • Lawrence D'Oliveiro

                              #29
                              Re: OOP / language design question

                              In article <1145969107.837 185.212970@e56g 2000cwe.googleg roups.com>,
                              "Carl Banks" <invalidemail@a erojockey.com> wrote:
                              [color=blue]
                              >bruno at modulix wrote:[color=green]
                              >> cctv.star@gmail .com wrote:[color=darkred]
                              >> > I was wondering, why you always have to remember to call bases'
                              >> > constructors[/color]
                              >>
                              >> <pedantic>
                              >> s/constructors/__init__/
                              >>
                              >> the __init__() method is *not* the constructor. Object's instanciation
                              >> is a two-stage process: __new__() is called first, then __init__().
                              >> </pedantic>[/color]
                              >
                              >You know, Python's __init__ has almost the same semantics as C++
                              >constructors (they both initialize something that's already been
                              >allocated in memory, and neither can return a substitute object). I
                              >actually think constructors are misnamed in C++, they should be called
                              >initializers (and destructors finalizers).[/color]

                              "Constructo r" is also the term used for the corresponding method in Java.

                              Is there any OO language that does not use "constructo r" in this sense?
                              I don't think there is one. This is standard OO terminology.

                              Comment

                              • Duncan Booth

                                #30
                                Re: OOP / language design question

                                Lawrence D'Oliveiro wrote:
                                [color=blue][color=green]
                                >>In other words, the object is constructed in Python before any
                                >>__init__ is called, but in C++ it isn't constructed until after all
                                >>the base class constructors have returned.[/color]
                                >
                                > But if "constructi on" is what a constructor does, then you're wrong.
                                >[/color]
                                I may be wrong (my C++ is getting rusty), but my belief is that if you have
                                a base class B and a derived class D, then until the B() constructor has
                                returned, the type of the object (as indicated by RTTI or by calling
                                virtual methods) is a B. It isn't until after the B constructor has
                                returned that the object is changed into a D.

                                This is different from Python's behaviour where the object is created as
                                its final type and then initialised.

                                Comment

                                Working...