OO conventions

Collapse
This topic is closed.
X
X
 
  • Time
  • Show
Clear All
new posts
  • Daniel Nogradi

    #1

    OO conventions

    I'm relatively new to object oriented programming, so get confused
    about its usage once in a while. Suppose there is a class Image that
    has a number of methods, rotate, open, verify, read, close, etc. Then
    to use this class my natural guess would be to have something like

    image = Image( )
    image.read( "myfile.jpg " )
    image.rotate( )
    image.close( )

    But now it turns out that the PIL module uses this as

    image = Image.open( "myfile.jpg " )
    image.verify( )
    image.rotate( )
    image.close( )

    Perhaps the real Image class of PIL doesn't have these methods
    exactly, but doesn't matter, my point is the way it works. Is it
    normal that instead of first creating an instance of a class, it
    starts right away with one its methods? I of course understand that
    the PIL people simply made a choice that their module works this way,
    period, but I'm just wondering if it wouldn't have been more "logical"
    to do it along the way of my first example.

    I guess it's just a matter of convention or how the programmer feels
    like, but there are no conventions of this type? Which would be more
    pythonic? Or I shouldn't worry and it's totally up to the developer?
  • Anthony Greene

    #2
    Re: OO conventions

    On Wed, 01 Feb 2006 23:40:37 +0100, Daniel Nogradi wrote:
    [color=blue]
    > I'm relatively new to object oriented programming, so get confused
    > about its usage once in a while. Suppose there is a class Image that
    > has a number of methods, rotate, open, verify, read, close, etc. Then
    > to use this class my natural guess would be to have something like
    >
    > image = Image( )
    > image.read( "myfile.jpg " )
    > image.rotate( )
    > image.close( )
    >
    > But now it turns out that the PIL module uses this as
    >
    > image = Image.open( "myfile.jpg " )
    > image.verify( )
    > image.rotate( )
    > image.close( )
    >
    > Perhaps the real Image class of PIL doesn't have these methods
    > exactly, but doesn't matter, my point is the way it works. Is it
    > normal that instead of first creating an instance of a class, it
    > starts right away with one its methods? I of course understand that
    > the PIL people simply made a choice that their module works this way,
    > period, but I'm just wondering if it wouldn't have been more "logical"
    > to do it along the way of my first example.
    >
    > I guess it's just a matter of convention or how the programmer feels
    > like, but there are no conventions of this type? Which would be more
    > pythonic? Or I shouldn't worry and it's totally up to the developer?[/color]


    You should take a look at the PIL source files, open() could surely be a
    class which takes a filename as an argument at construction time. If you
    run across such code, there is really no need to worry, some things are
    conventional and some are a matter of preference. In most cases diving
    into the source can help clarify a lot of your speculations, better hope
    the code is transparent, i hate reading non-readable code. :)

    --
    A wise man knows he knows nothing.

    Comment

    • Anthony Greene

      #3
      Re: OO conventions

      On Wed, 01 Feb 2006 23:40:37 +0100, Daniel Nogradi wrote:
      [color=blue]
      > I'm relatively new to object oriented programming, so get confused
      > about its usage once in a while. Suppose there is a class Image that
      > has a number of methods, rotate, open, verify, read, close, etc. Then
      > to use this class my natural guess would be to have something like
      >
      > image = Image( )
      > image.read( "myfile.jpg " )
      > image.rotate( )
      > image.close( )
      >
      > But now it turns out that the PIL module uses this as
      >
      > image = Image.open( "myfile.jpg " )
      > image.verify( )
      > image.rotate( )
      > image.close( )
      >
      > Perhaps the real Image class of PIL doesn't have these methods
      > exactly, but doesn't matter, my point is the way it works. Is it
      > normal that instead of first creating an instance of a class, it
      > starts right away with one its methods? I of course understand that
      > the PIL people simply made a choice that their module works this way,
      > period, but I'm just wondering if it wouldn't have been more "logical"
      > to do it along the way of my first example.
      >
      > I guess it's just a matter of convention or how the programmer feels
      > like, but there are no conventions of this type? Which would be more
      > pythonic? Or I shouldn't worry and it's totally up to the developer?[/color]

      In this case, Image seems to be a python module, with the open function
      defined, PIL's Image is not a class.

      --
      A wise man knows he knows nothing.

      Comment

      • Kirk McDonald

        #4
        Re: OO conventions

        Daniel Nogradi wrote:[color=blue]
        > I'm relatively new to object oriented programming, so get confused
        > about its usage once in a while. Suppose there is a class Image that
        > has a number of methods, rotate, open, verify, read, close, etc. Then
        > to use this class my natural guess would be to have something like
        >
        > image = Image( )
        > image.read( "myfile.jpg " )
        > image.rotate( )
        > image.close( )
        >
        > But now it turns out that the PIL module uses this as
        >
        > image = Image.open( "myfile.jpg " )
        > image.verify( )
        > image.rotate( )
        > image.close( )
        >
        > Perhaps the real Image class of PIL doesn't have these methods
        > exactly, but doesn't matter, my point is the way it works. Is it
        > normal that instead of first creating an instance of a class, it
        > starts right away with one its methods? I of course understand that
        > the PIL people simply made a choice that their module works this way,
        > period, but I'm just wondering if it wouldn't have been more "logical"
        > to do it along the way of my first example.
        >
        > I guess it's just a matter of convention or how the programmer feels
        > like, but there are no conventions of this type? Which would be more
        > pythonic? Or I shouldn't worry and it's totally up to the developer?[/color]


        If I were coding a class like that, I'd probably do it like this:

        image = Image("myfile.j pg")
        image.rotate()
        image.close()

        And, in addition, there would be an image.open() method. In the above,
        __init__ just calls it on its own when the filename is specified.

        (Though, of course, I'm not at all familiar with the PIL module; there
        might be more stuff going on behind the scenes with the way they do it.)

        -Kirk McDonald

        Comment

        • Claudio Grondi

          #5
          Re: OO conventions

          Daniel Nogradi wrote:[color=blue]
          > I'm relatively new to object oriented programming, so get confused
          > about its usage once in a while. Suppose there is a class Image that
          > has a number of methods, rotate, open, verify, read, close, etc. Then
          > to use this class my natural guess would be to have something like
          >
          > image = Image( )
          > image.read( "myfile.jpg " )
          > image.rotate( )
          > image.close( )
          >
          > But now it turns out that the PIL module uses this as
          >
          > image = Image.open( "myfile.jpg " )
          > image.verify( )
          > image.rotate( )
          > image.close( )
          >
          > Perhaps the real Image class of PIL doesn't have these methods
          > exactly, but doesn't matter, my point is the way it works. Is it
          > normal that instead of first creating an instance of a class, it
          > starts right away with one its methods? I of course understand that
          > the PIL people simply made a choice that their module works this way,
          > period, but I'm just wondering if it wouldn't have been more "logical"
          > to do it along the way of my first example.
          >
          > I guess it's just a matter of convention or how the programmer feels
          > like, but there are no conventions of this type? Which would be more
          > pythonic? Or I shouldn't worry and it's totally up to the developer?[/color]
          It's probably more up to the context than to the developer.

          I can imagine, that the reason for not using
          image = Image()
          is, that 'image' would be in a bad defined state suggesting to be an
          Image(), but after image = Image() it were actually not (yet) an image.

          It makes sense to me, to have an actual image object with some pixel
          width and height and some color scheme and not only undefined empty
          container for an image. In another context the approach you describe is
          maybe more appropriate, but in case of an Image() object I can't see any
          advantage.

          I don't know if there exist any conventions for how to handle such
          matters. For me Pythonic means it should be as I expect it intuitively.
          And my intuition tells me, that I need to define what image I want, when
          I create an Image object.

          Maybe it would even be more appropriate to pass the definition of the
          image to the Image class using Image(fileObjec t) or Image(fileNameS pec)
          or Image(imageForm atAndSizeSpec), but I can imagine, that using
          different methods instead of detection of the type of parameter passed
          is probably less error prone making a good reason supporting the
          approach of using Image.open("bri de.jpg") or Image.new(mode, size) for
          creation of an Image object when it is defined in different ways.

          As the .new() and .open() methods are anyway there in order to be
          available for further image operations, why not using them directly in
          the process of creating Image() objects?

          Claudio

          Comment

          • Claudio Grondi

            #6
            Re: OO conventions

            Daniel Nogradi wrote:[color=blue]
            > I'm relatively new to object oriented programming, so get confused
            > about its usage once in a while. Suppose there is a class Image that
            > has a number of methods, rotate, open, verify, read, close, etc. Then
            > to use this class my natural guess would be to have something like
            >
            > image = Image( )
            > image.read( "myfile.jpg " )
            > image.rotate( )
            > image.close( )
            >
            > But now it turns out that the PIL module uses this as
            >
            > image = Image.open( "myfile.jpg " )
            > image.verify( )
            > image.rotate( )
            > image.close( )
            >
            > Perhaps the real Image class of PIL doesn't have these methods
            > exactly, but doesn't matter, my point is the way it works. Is it
            > normal that instead of first creating an instance of a class, it
            > starts right away with one its methods? I of course understand that
            > the PIL people simply made a choice that their module works this way,
            > period, but I'm just wondering if it wouldn't have been more "logical"
            > to do it along the way of my first example.
            >
            > I guess it's just a matter of convention or how the programmer feels
            > like, but there are no conventions of this type? Which would be more
            > pythonic? Or I shouldn't worry and it's totally up to the developer?[/color]
            It's probably more up to the context than to the developer.

            I can imagine, that the reason for not using
            image = Image()
            is, that 'image' would be in a bad defined state suggesting to be an
            Image(), but after image = Image() it were actually not (yet) an image.

            It makes sense to me, to have an actual image object with some pixel
            width and height and some color scheme and not only undefined empty
            container for an image. In another context the approach you describe is
            maybe more appropriate, but in case of an Image() object I can't see any
            advantage.

            I don't know if there exist any conventions for how to handle such
            matters. For me Pythonic means it should be as I expect it intuitively.
            And my intuition tells me, that I need to define what image I want, when
            I create an Image object.

            Maybe it would even be more appropriate to pass the definition of the
            image to the Image class using Image(fileObjec t) or Image(fileNameS pec)
            or Image(imageForm atAndSizeSpec), but I can imagine, that using
            different methods instead of detection of the type of parameter passed
            is probably less error prone making a good reason supporting the
            approach of using Image.open("bri de.jpg") or Image.new(mode, size) for
            creation of an Image object when it is defined in different ways.

            As the .new() and .open() methods are anyway there in order to be
            available for further image operations, why not using them directly in
            the process of creating Image() objects?

            Claudio

            Comment

            • Daniel Nogradi

              #7
              Re: OO conventions

              >[color=blue]
              > In this case, Image seems to be a python module, with the open function
              > defined, PIL's Image is not a class.
              >[/color]

              Thanks for the enlightening remarks, especially this last one, indeed,
              it's not a class.

              Comment

              • Steven D'Aprano

                #8
                Re: OO conventions

                On Wed, 01 Feb 2006 23:40:37 +0100, Daniel Nogradi wrote:
                [color=blue]
                > I'm relatively new to object oriented programming, so get confused
                > about its usage once in a while. Suppose there is a class Image that
                > has a number of methods, rotate, open, verify, read, close, etc. Then
                > to use this class my natural guess would be to have something like
                >
                > image = Image( )[/color]

                Now you have an "image" object. What is it?

                Answer: it isn't an image at all, not in the plain English sense. (Or if
                it is, it is an arbitrary "default image" picked by the class designer.)
                [color=blue]
                > image.read( "myfile.jpg " )[/color]

                And now, at long last, the image object actually is an image. So why make
                this a two step process? Whatever the Image() initialization does, why
                can't it be done automatically when you read the file?

                [snip]
                [color=blue]
                > Perhaps the real Image class of PIL doesn't have these methods exactly,
                > but doesn't matter, my point is the way it works. Is it normal that
                > instead of first creating an instance of a class, it starts right away
                > with one its methods? I of course understand that the PIL people simply
                > made a choice that their module works this way, period, but I'm just
                > wondering if it wouldn't have been more "logical" to do it along the way
                > of my first example.[/color]

                It seems to me that the choice depends on the data you are handling, plus
                of course the class designer's choice. (Dare I say it, whim.)

                If a class has a natural, obvious default state (e.g. a mutable string
                class might start off empty, a mutable int class might start off as zero,
                a binary tree might start off as an empty node with no children) then it
                makes sense to initialise the class, then add your data.

                But if the class has no natural default state, then it makes no sense to
                create an "empty object" with no data, a "non-image image" so to speak.

                In other words, if you find yourself writing methods like this:

                class Klass:
                def foo(self):
                if self.data is None:
                raise KlassError("Can 't foo an uninitialized Klass object.")
                else:
                # do something

                then you are just doing pointless make-work to fit a convention that
                doesn't make sense for your class.



                --
                Steven.

                Comment

                • Tim N. van der Leeuw

                  #9
                  Re: OO conventions


                  Daniel Nogradi wrote:[color=blue][color=green]
                  > >
                  > > In this case, Image seems to be a python module, with the open function
                  > > defined, PIL's Image is not a class.
                  > >[/color]
                  >
                  > Thanks for the enlightening remarks, especially this last one, indeed,
                  > it's not a class.[/color]

                  Actually, this way of creating a class instance is good OO practice in
                  many places: The Image.open() method acts as a factory-function for
                  creating Image objects.
                  You don't know, until you inspect the return value, if the created
                  object is actually an instance of class Image or something which just
                  happens to behave like one...
                  It could for instance be a sub-class of Image, JpegImage, if that
                  better suits the class library designers.
                  In general, factory-functions allow greater uncoupling between
                  interface and implementation.

                  Regards,

                  --Tim

                  Comment

                  • Alex Martelli

                    #10
                    Re: OO conventions

                    Steven D'Aprano <steve@REMOVETH IScyber.com.au> wrote:[color=blue][color=green]
                    > > image = Image( )[/color]
                    >
                    > Now you have an "image" object. What is it?
                    >
                    > Answer: it isn't an image at all, not in the plain English sense. (Or if
                    > it is, it is an arbitrary "default image" picked by the class designer.)[/color]

                    No doubt (presumably some kind of as-yet blank one).
                    [color=blue][color=green]
                    > > image.read( "myfile.jpg " )[/color]
                    >
                    > And now, at long last, the image object actually is an image. So why make
                    > this a two step process? Whatever the Image() initialization does, why
                    > can't it be done automatically when you read the file?[/color]

                    "Two-step construct" ("2SC") is a reasonably well-known and widely
                    useful idiom, and it can serve several kinds of purposes, many but not
                    all of which are tied to persistence. For example, to some extent it's
                    used in Entity Enterprise Javabeans (EJBs): with CMP, instead of having
                    to create a separate object each time you need a new record from the
                    database, the container can juggle a pool of objects and reuse each of
                    them to hold different records at different times, so the number of
                    beans needed is the number of different records you need to have
                    _simultaneously _. The "thread pool" concept can be implemented in a
                    similar way, although this is less common. Another field where 2SC is
                    often used is GUIs; in that case the main motivation is "impedence
                    matching" between a GUI toolkit (often a cross-platform one) and a given
                    platform's underlying toolkit.

                    Python offers good opportunities to implement 2SC "under the covers"
                    thanks to the split between __new__ and __init__: indeed one can get
                    tricky, since __new__ need not necessarily perform the first step of 2SC
                    (it might well return a "scrubbed" object from the pool rather than the
                    new one). Unpickling may normally use __setstate__ instead of __init__
                    (after __new__, anyway) -- that's more flexible and often easier to
                    arrange than going through getinitargs (which doesn't work for newstyle
                    classes anyway) or getnewargs (which doesn't work for classic ones).

                    Of course, the way OOP is normally taught, 2SC sounds like a heresy, but
                    "out in the real world" it does have some advantages (even though
                    single-step construction remains the normal approach in most cases).
                    [color=blue]
                    > But if the class has no natural default state, then it makes no sense to
                    > create an "empty object" with no data, a "non-image image" so to speak.[/color]

                    Hmmm, it might, actually; that's what __new__ normally does for
                    instances of mutable classes, so that __init__ or __setstate__ or other
                    different methods yet, as appropriate, can then make the object
                    "nonempty" in different ways.

                    [color=blue]
                    > In other words, if you find yourself writing methods like this:
                    >
                    > class Klass:
                    > def foo(self):
                    > if self.data is None:
                    > raise KlassError("Can 't foo an uninitialized Klass object.")
                    > else:
                    > # do something
                    >
                    > then you are just doing pointless make-work to fit a convention that
                    > doesn't make sense for your class.[/color]

                    It does appear to be a code smell, yes. But protocol semantics may
                    often require such constraints as "it does not make sense to call x.A
                    unless x.B has been previously called" or viceversa "it's forbidden to
                    call x.A if x.B has already been called", and such constraints are
                    generally implemented through something like the above idiom.

                    Consider a file object, for example: after you call f.close() any other
                    method call must raise an error. The simplest, most natural way to
                    implement this is very similar to what you just coded, using a
                    self.closed flag -- a case of "two-step destruction", separating
                    termination ("close") from finalization (destruction proper).

                    Reusable objects, that can be born "scrubbed", loaded with some data and
                    used for a while, then "scrubbed" again (with the data getting persisted
                    off and the object surviving and going into a reuse-pool), typically
                    need a flag to know if they're scrubbed or not (or, some other data
                    member may play double duty, e.g. by being None iff an object is
                    scrubbed). All normal methods must then raise if called on a scrubbed
                    object; code for that purpose may be injected by decorators or a custom
                    metaclass to reduce the boilerplate and code smells.

                    Note that I'm not defending the OP's contention -- I've seen no reason
                    in his post making 2SC/2SD desirable. I'm just addressing the wider
                    issue... one I probably wouldn't even cover in "OOP 101", but hold for a
                    later course, e.g. one on the architecture of persistence frameworks.


                    Alex

                    Comment

                    • Magnus Lycka

                      #11
                      Re: OO conventions

                      Alex Martelli wrote:[color=blue]
                      > Steven D'Aprano <steve@REMOVETH IScyber.com.au> wrote:[color=green]
                      >>And now, at long last, the image object actually is an image. So why make
                      >>this a two step process? Whatever the Image() initialization does, why
                      >>can't it be done automatically when you read the file?[/color]
                      >
                      > "Two-step construct" ("2SC") is a reasonably well-known and widely
                      > useful idiom, and it can serve several kinds of purposes, many but not
                      > all of which are tied to persistence.[/color]

                      I've seen it used as a C++ coding standard when e.g. loading
                      instances from databases (Person p=Person(); p.init(id);)
                      since it was considered bad to have code in the constructor
                      which could throw exceptions. If you make a new Person on
                      the heap, and catch an exception from the constructor, are
                      you sure that you delete the object?

                      Comment

                      • Alex Martelli

                        #12
                        Re: OO conventions

                        Magnus Lycka <lycka@carmen.s e> wrote:
                        [color=blue]
                        > Alex Martelli wrote:[color=green]
                        > > Steven D'Aprano <steve@REMOVETH IScyber.com.au> wrote:[color=darkred]
                        > >>And now, at long last, the image object actually is an image. So why make
                        > >>this a two step process? Whatever the Image() initialization does, why
                        > >>can't it be done automatically when you read the file?[/color]
                        > >
                        > > "Two-step construct" ("2SC") is a reasonably well-known and widely
                        > > useful idiom, and it can serve several kinds of purposes, many but not
                        > > all of which are tied to persistence.[/color]
                        >
                        > I've seen it used as a C++ coding standard when e.g. loading
                        > instances from databases (Person p=Person(); p.init(id);)
                        > since it was considered bad to have code in the constructor
                        > which could throw exceptions. If you make a new Person on
                        > the heap, and catch an exception from the constructor, are
                        > you sure that you delete the object?[/color]

                        Yes, this is a language-specific issue that applies to C++. But I tried
                        to show that 2SC (and its counterpart, 2SD) can come in handy in many
                        other languages, too, including Python.


                        Alex

                        Comment

                        • Daniel Nogradi

                          #13
                          Re: OO conventions

                          > > > In this case, Image seems to be a python module, with the open function[color=blue][color=green][color=darkred]
                          > > > defined, PIL's Image is not a class.
                          > > >[/color]
                          > >
                          > > Thanks for the enlightening remarks, especially this last one, indeed,
                          > > it's not a class.[/color]
                          >
                          > Actually, this way of creating a class instance is good OO practice in
                          > many places: The Image.open() method acts as a factory-function for
                          > creating Image objects.
                          > You don't know, until you inspect the return value, if the created
                          > object is actually an instance of class Image or something which just
                          > happens to behave like one...
                          > It could for instance be a sub-class of Image, JpegImage, if that
                          > better suits the class library designers.
                          > In general, factory-functions allow greater uncoupling between
                          > interface and implementation.
                          >[/color]
                          [color=blue]
                          >From all the enlightening replies it seems to me that my next task[/color]
                          should be to look into what 'factory functions' are. Whenever I've
                          seen the word 'factory' I just skipped it, knowing that if I really
                          need to know what it is, I'll come back to it later. Apparently it's
                          just about time, thanks all for the comments.

                          Comment

                          • Blair P. Houghton

                            #14
                            Re: OO conventions

                            Image would be a superclass to JPGImage, BMPImage, PNGImage, etc...

                            But which to use could only be determined AFTER opening the file,
                            because "file.jpg" doesn't have type JPG, it has type string and
                            semantic value "maybe a jpeg file or maybe something misnamed as a jpeg
                            file".

                            So Image.open(file name) seems right as a factory function that opens
                            the file, figures out what it really is, constructs the appropriate
                            subclass object (likely by passing a string to the constructor, e.g.,
                            JPGImage(filena me)), and returns the object via the superclass type.
                            The caller can then either check a flag in the superclass to see what
                            type the subclass is, or just assume it's the right type of image for
                            the filename extension (or does Python have RTTI? I don't recall if
                            I've seen it, yet...).

                            Though if the filename doesn't match the content, someone should raise
                            an exception...

                            But this means that Image.open(file name) is a static method of the
                            superclass, not requiring instantiation. Image(string) could easily
                            default to assuming string is a filename, doing the same work as
                            Image.open(file name), though it would at least partially construct an
                            Image instance each time it's called, which isn't what you want.
                            Image.open(file name) defined as a static method (wait...is that
                            possible in Python? I hate being the newbie) would not do any
                            constructing until after it knew what to construct.

                            --Blair

                            Comment

                            • Steven D'Aprano

                              #15
                              Re: OO conventions

                              On Thu, 02 Feb 2006 17:00:26 -0800, Blair P. Houghton wrote:
                              [color=blue]
                              > Image would be a superclass to JPGImage, BMPImage, PNGImage, etc...
                              >
                              > But which to use could only be determined AFTER opening the file,
                              > because "file.jpg" doesn't have type JPG, it has type string and
                              > semantic value "maybe a jpeg file or maybe something misnamed as a jpeg
                              > file".[/color]

                              Absolutely.
                              [color=blue]
                              > So Image.open(file name) seems right as a factory function that opens
                              > the file, figures out what it really is, constructs the appropriate
                              > subclass object (likely by passing a string to the constructor, e.g.,
                              > JPGImage(filena me)), and returns the object via the superclass type.
                              > The caller can then either check a flag in the superclass to see what
                              > type the subclass is, or just assume it's the right type of image for
                              > the filename extension (or does Python have RTTI? I don't recall if
                              > I've seen it, yet...).[/color]

                              RTTI = Run Time Type Information, yes?

                              Objects in Python are strongly typed, if that's what you mean. So if each
                              image kind (jpeg, tiff, png, etc.) is a subclass, then you can easily use
                              introspection to find out which subclass it is.


                              [color=blue]
                              > Though if the filename doesn't match the content, someone should raise
                              > an exception...[/color]

                              That depends on whether you believe DOS style filename extensions are
                              significant, or that they are a poor substitute for real metadata.

                              If you come from Windows land, you might thing the file extension is the
                              file type. If you come from Linux land, you might think that file
                              extensions are just a label stuck on the file name as a convenience for
                              the user. If you come from Macintosh land, especially pre-OS X, you
                              probably think that file extensions are at best a useful redundancy and at
                              worst an abomination.

                              From a UI perspective, an application should never refuse to handle a file
                              because of a mismatch between the file type contents, the file type
                              metadata (if any), and the file extension, with one important proviso as
                              follows. In general, if your application handles (say) both JPEGs and
                              GIFs, and the user tries to open a JPEG named foo.gif, then the correct
                              action is to advise the user that the file is actually a JPEG, and
                              give them the opportunity to open the file anyway.

                              Whether that is best handed internally by making Image.read() raise an
                              exception or not is a question for the class designer.

                              The proviso is, if there are potential security implications of that
                              mismatch (e.g. an executable file masquerading as a .text file), and the
                              target audience is technically naive, then I believe it may be appropriate
                              for the application to refuse to handle the file. Don't let the user shoot
                              themselves in the foot if they aren't technically sophisticated enough to
                              realise they are shooting themselves in the foot.


                              --
                              Steven.

                              Comment

                              Working...