Instances behaviour

Collapse
This topic is closed.
X
X
 
  • Time
  • Show
Clear All
new posts
  • Mr.Rech

    #1

    Instances behaviour

    Hi all,
    I've been using Python for 3 years, but I've rarely used its OOP
    features (I'm a physicist, sorry). Now, after having read a lot about
    Python OOP capabilities, I'm trying to get advantage of this (for me)
    new paradigm. As a result I've a lot of somewhat philosophical
    questions. I will start with one of them.

    Suppose I have a bunch of classes that represent slightly (but
    conceptually) different object. The instances of each class must behave
    in very similar manner, so that I've created a common class ancestor
    (let say A) that define a lot of special method (such as __getattr__,
    __setattr__, __len__ and so on), and then I've created all my "real"
    classes inheriting from it:
    [color=blue][color=green][color=darkred]
    >>>class A(object):[/color][/color][/color]
    ..... # here define all special and some common methods
    [color=blue][color=green][color=darkred]
    >>> class B(A):[/color][/color][/color]
    ..... # this is the first "real" class
    [color=blue][color=green][color=darkred]
    >>> class C(A):[/color][/color][/color]
    ..... # and this is the second

    and so on. The problem I'm worried about is that an unaware user may
    create an instance of "A" supposing that it has any real use, while it
    is only a sort of prototype. However, I can't see (from my limited
    point of view) any other way to rearrange things and still get a
    similar behaviour.

    Implementing those special methods directly in class B and then inherit
    from it, doesn't seem the right way, since I'd prefer that instances of
    B weren't in any relation with the instances of C (i.e. I'd prefer not
    to subclass C from B)

    Perhaps some OOP techniques (that I miss at the moment) could be of any
    help. Any suggestion?

    Thanks in advance,
    Andrea.

  • Inyeol Lee

    #2
    Re: Instances behaviour

    On Thu, Dec 01, 2005 at 03:51:05PM -0800, Mr.Rech wrote:
    [...][color=blue]
    > Suppose I have a bunch of classes that represent slightly (but
    > conceptually) different object. The instances of each class must behave
    > in very similar manner, so that I've created a common class ancestor
    > (let say A) that define a lot of special method (such as __getattr__,
    > __setattr__, __len__ and so on), and then I've created all my "real"
    > classes inheriting from it:
    >[color=green][color=darkred]
    > >>>class A(object):[/color][/color]
    > .... # here define all special and some common methods
    >[color=green][color=darkred]
    > >>> class B(A):[/color][/color]
    > .... # this is the first "real" class
    >[color=green][color=darkred]
    > >>> class C(A):[/color][/color]
    > .... # and this is the second
    >
    > and so on. The problem I'm worried about is that an unaware user may
    > create an instance of "A" supposing that it has any real use, while it
    > is only a sort of prototype. However, I can't see (from my limited
    > point of view) any other way to rearrange things and still get a
    > similar behaviour.[/color]
    [color=blue][color=green][color=darkred]
    >>> class A(object):
    >>> ... def __init__(self, foo):
    >>> ... if self.__class__ is A:
    >>> ... raise TypeError("A is base class.")
    >>> ... self.foo = foo
    >>> ...
    >>> class B(A):[/color][/color][/color]
    .... pass
    ....[color=blue][color=green][color=darkred]
    >>> class C(A):[/color][/color][/color]
    .... def __init__(self, foo, bar):
    .... A.__init__(self , foo)
    .... self.bar = bar
    ....[color=blue][color=green][color=darkred]
    >>> a = A(1)[/color][/color][/color]
    Traceback (most recent call last):
    File "<stdin>", line 1, in ?
    File "<stdin>", line 4, in __init__
    TypeError: A is base class.[color=blue][color=green][color=darkred]
    >>> b = B(1)
    >>> b.foo[/color][/color][/color]
    1[color=blue][color=green][color=darkred]
    >>> c = C(1, 2)
    >>> c.foo, c.bar[/color][/color][/color]
    (1, 2)[color=blue][color=green][color=darkred]
    >>>[/color][/color][/color]

    HTH
    --Inyeol Lee

    Comment

    • Mike Meyer

      #3
      Re: Instances behaviour

      "Mr.Rech" <andrea.riciput i@gmail.com> writes:[color=blue]
      > Suppose I have a bunch of classes that represent slightly (but
      > conceptually) different object. The instances of each class must behave
      > in very similar manner, so that I've created a common class ancestor
      > (let say A) that define a lot of special method (such as __getattr__,
      > __setattr__, __len__ and so on), and then I've created all my "real"
      > classes inheriting from it:
      >
      > and so on. The problem I'm worried about is that an unaware user may
      > create an instance of "A" supposing that it has any real use, while it
      > is only a sort of prototype. However, I can't see (from my limited
      > point of view) any other way to rearrange things and still get a
      > similar behaviour.
      >
      > Perhaps some OOP techniques (that I miss at the moment) could be of any
      > help. Any suggestion?[/color]

      I assume there are methods of B & C that aren't shared, and hence
      aren't in A. When the user invokes those, they should get an error
      message. That's how this kind of thing is normally dealt with.

      If you want things to happen at instantiation time, then you can make
      A.__init__ raise an exception. Your B & C __init__ then can't invoke
      it. If A.__init__ has a real use, move that into another method that B
      & C's __init__ can invoke.

      <mike
      --
      Mike Meyer <mwm@mired.or g> http://www.mired.org/home/mwm/
      Independent WWW/Perforce/FreeBSD/Unix consultant, email for more information.

      Comment

      • Peter Otten

        #4
        Re: Instances behaviour

        Mr.Rech wrote:
        [color=blue]
        > Suppose I have a bunch of classes that represent slightly (but
        > conceptually) different object. The instances of each class must behave
        > in very similar manner, so that I've created a common class ancestor
        > (let say A) that define a lot of special method (such as __getattr__,
        > __setattr__, __len__ and so on), and then I've created all my "real"
        > classes inheriting from it:
        >[color=green][color=darkred]
        >>>>class A(object):[/color][/color]
        > ....     # here define all special and some common methods
        >[color=green][color=darkred]
        >>>> class B(A):[/color][/color]
        > ....    # this is the first "real" class
        >[color=green][color=darkred]
        >>>> class C(A):[/color][/color]
        > ....    # and this is the second
        >
        > and so on. The problem I'm worried about is that an unaware user may
        > create an instance of "A" supposing that it has any real use, while it
        > is only a sort of prototype. However, I can't see (from my limited
        > point of view) any other way to rearrange things and still get a
        > similar behaviour.
        >
        > Implementing those special methods directly in class B and then inherit
        > from it, doesn't seem the right way, since I'd prefer that instances of
        > B weren't in any relation with the instances of C (i.e. I'd prefer not
        > to subclass C from B)
        >
        > Perhaps some OOP techniques (that I miss at the moment) could be of any
        > help. Any suggestion?[/color]

        How about

        class A(object):
        """Provides common functionality for A-like classes, e. g. B and C.

        Do not instantiate.
        """

        This is definitely a low-tech approach, but I suppose you don't clutter your
        functions with spurious argument type checks, either.
        An exception like the one shown by Inyeol Lee is typically raised once
        during the development process while my approach bites (only) the
        illiterate programmer with a message like
        [color=blue][color=green][color=darkred]
        >>> a.foo()[/color][/color][/color]
        Traceback (most recent call last):
        File "<stdin>", line 1, in ?
        AttributeError: 'A' object has no attribute 'foo'

        which normally can be tracked down almost as quickly -- and which serves him
        well anyway :-)

        Peter

        Comment

        • bruno at modulix

          #5
          Re: Instances behaviour

          Inyeol Lee wrote:
          (snip)
          [color=blue][color=green][color=darkred]
          >>>>class A(object):
          >>>>... def __init__(self, foo):
          >>>>... if self.__class__ is A:
          >>>>... raise TypeError("A is base class.")[/color][/color][/color]


          s/TypeError/NotImplementedE rror/
          s/base class/abstract class/


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

          Comment

          • Mr.Rech

            #6
            Re: Instances behaviour

            Thanks for your suggestions. They are very usefull and indeed bypass my
            problem. However, I've found a (perhaps) more elegant way to get the
            same result using metaclasses. My idea is to define my classes as
            follows:
            [color=blue][color=green][color=darkred]
            >>> class meta_A(type):[/color][/color][/color]
            ..... def __new__(cls, classname, bases, classdict):
            # define here all special methods
            newdict = { #special methods dict}
            classdict.updat e(newdict) # any suggestion on
            automatically build newdict?
            return type.__new__(cl s, classname, bases, classdict)
            [color=blue][color=green][color=darkred]
            >>> class B(object):[/color][/color][/color]
            __metaclass__ = meta_A
            # More methods here

            I know metaclasses are a complete different beast, anyway I find this
            approach more pythonic. Any comment? Suggestion?

            Thanks,
            Andrea.

            Comment

            • Peter Otten

              #7
              Re: Instances behaviour

              Mr.Rech wrote:
              [color=blue]
              > Thanks for your suggestions. They are very usefull and indeed bypass my
              > problem. However, I've found a (perhaps) more elegant way to get the
              > same result using metaclasses. My idea is to define my classes as
              > follows:
              >[color=green][color=darkred]
              >>>> class meta_A(type):[/color][/color]
              > .... def __new__(cls, classname, bases, classdict):
              > # define here all special methods
              > newdict = { #special methods dict}
              > classdict.updat e(newdict) # any suggestion on
              > automatically build newdict?
              > return type.__new__(cl s, classname, bases, classdict)[/color]

              Are you intentionally defeating inheritance?
              [color=blue][color=green][color=darkred]
              >>>> class B(object):[/color][/color]
              > __metaclass__ = meta_A
              > # More methods here
              >
              > I know metaclasses are a complete different beast, anyway I find this
              > approach more pythonic. Any comment? Suggestion?[/color]

              Godawful.

              Don't use classes when functions suffice.
              Don't use inheritance when duck-typing suffices.
              Don't use metaclasses when inheritance suffices.
              Corollary: don't use metaclasses to solve problems you have just made up.

              In short, the simplest solution is most likely the most pythonic, even when
              some odd corner cases are not covered.

              Simplicity also has a nice side effect: fewer bugs.

              Peter

              Comment

              • bruno at modulix

                #8
                Re: Instances behaviour

                Mr.Rech wrote:[color=blue]
                > Thanks for your suggestions. They are very usefull and indeed bypass my
                > problem. However, I've found a (perhaps) more elegant way to get the
                > same result using metaclasses.[/color]


                (snip code)[color=blue]
                >
                > I know metaclasses are a complete different beast, anyway I find this
                > approach more pythonic.[/color]

                It's not. It's a case of ArbitraryOverco mplexification( tm).

                The pythonic way is to use inheritence and make the base class abstract
                by raising a NotImplementedE rror in it's __init__ (cf Inyeol Lee's
                answer and my small correction)


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

                Comment

                • Mr.Rech

                  #9
                  Re: Instances behaviour

                  I see your point. Looking again at my metaclass implementation and
                  comparing it with your abstract class + inheritance approach it turns
                  out that the latter is definetively more straightforward , easier to
                  maintain and all in all more pythonic.

                  Sorry, but being an OOP newbie put me in the position of
                  overcomplexifin g(tm) things a little bit. I'll be back soon with other
                  (I hope less silly) questions. ;-p

                  Thanks for all your suggestions,
                  Andrea

                  Comment

                  • Inyeol Lee

                    #10
                    Re: Instances behaviour

                    On Fri, Dec 02, 2005 at 10:43:56AM +0100, bruno at modulix wrote:[color=blue]
                    > Inyeol Lee wrote:
                    > (snip)
                    >[color=green][color=darkred]
                    > >>>>class A(object):
                    > >>>>... def __init__(self, foo):
                    > >>>>... if self.__class__ is A:
                    > >>>>... raise TypeError("A is base class.")[/color][/color]
                    >
                    >
                    > s/TypeError/NotImplementedE rror/
                    > s/base class/abstract class/[/color]

                    I prefer TypeError here, NotImplementedE rror would be OK though.
                    Here is an example from sets.py in stdlib.


                    class BaseSet(object) :
                    """Common base class for mutable and immutable sets."""

                    __slots__ = ['_data']

                    # Constructor

                    def __init__(self):
                    """This is an abstract class."""
                    # Don't call this from a concrete subclass!
                    if self.__class__ is BaseSet:
                    raise TypeError, ("BaseSet is an abstract class. "
                    "Use Set or ImmutableSet.")


                    Inyeol

                    Comment

                    • Giovanni Bajo

                      #11
                      Re: Instances behaviour

                      Mr.Rech wrote:
                      [color=blue]
                      > and so on. The problem I'm worried about is that an unaware user may
                      > create an instance of "A" supposing that it has any real use, while it
                      > is only a sort of prototype. However, I can't see (from my limited
                      > point of view) any other way to rearrange things and still get a
                      > similar behaviour.[/color]


                      1) Document your class is not intended for public use.
                      2) Make your A class "private" of the module that defines it. A simple way is
                      putting an underscore in front of its name.
                      3) Make your A class non-functional. I assume B and C have methods that A
                      doesn't. Then, add those methods to A too, but not implement them:

                      def foo(self):
                      """Foo this and that. Must be implemented in subclasses."""
                      raise NotImplementedE rror

                      --
                      Giovanni Bajo


                      Comment

                      Working...