Groups | Search | Server Info | Keyboard shortcuts | Login | Register [http] [https] [nntp] [nntps]


Groups > comp.lang.python > #44944 > unrolled thread

object.enable() anti-pattern

Started bySteven D'Aprano <steve+comp.lang.python@pearwood.info>
First post2013-05-08 08:52 +0000
Last post2013-05-13 08:15 -0500
Articles 20 on this page of 82 — 23 participants

Back to article view | Back to comp.lang.python


Contents

  object.enable() anti-pattern Steven D'Aprano <steve+comp.lang.python@pearwood.info> - 2013-05-08 08:52 +0000
    Re: object.enable() anti-pattern Christian Heimes <christian@python.org> - 2013-05-08 11:51 +0200
    Re: object.enable() anti-pattern Robert Kern <robert.kern@gmail.com> - 2013-05-08 11:13 +0100
      Re: object.enable() anti-pattern Steven D'Aprano <steve+comp.lang.python@pearwood.info> - 2013-05-08 12:30 +0000
    Re: object.enable() anti-pattern Roy Smith <roy@panix.com> - 2013-05-08 09:17 -0400
    Re: object.enable() anti-pattern Duncan Booth <duncan.booth@invalid.invalid> - 2013-05-08 14:27 +0000
      Re: object.enable() anti-pattern Dan Sommers <dan@tombstonezero.net> - 2013-05-09 02:38 +0000
        Re: object.enable() anti-pattern Steven D'Aprano <steve+comp.lang.python@pearwood.info> - 2013-05-09 05:37 +0000
          Re: object.enable() anti-pattern Chris Angelico <rosuav@gmail.com> - 2013-05-09 15:52 +1000
      Re: object.enable() anti-pattern Steven D'Aprano <steve+comp.lang.python@pearwood.info> - 2013-05-09 03:12 +0000
    Re: object.enable() anti-pattern Dan Sommers <dan@tombstonezero.net> - 2013-05-09 02:42 +0000
      Re: object.enable() anti-pattern Steven D'Aprano <steve+comp.lang.python@pearwood.info> - 2013-05-09 05:23 +0000
        Re: object.enable() anti-pattern Terry Jan Reedy <tjreedy@udel.edu> - 2013-05-09 02:41 -0400
        Re: object.enable() anti-pattern Gregory Ewing <greg.ewing@canterbury.ac.nz> - 2013-05-09 19:54 +1200
          Re: object.enable() anti-pattern Cameron Simpson <cs@zip.com.au> - 2013-05-09 18:23 +1000
            Re: object.enable() anti-pattern Steven D'Aprano <steve+comp.lang.python@pearwood.info> - 2013-05-09 11:30 +0000
              Re: object.enable() anti-pattern Cameron Simpson <cs@zip.com.au> - 2013-05-10 09:36 +1000
                Re: object.enable() anti-pattern Steven D'Aprano <steve+comp.lang.python@pearwood.info> - 2013-05-10 05:00 +0000
                  Re: object.enable() anti-pattern Roy Smith <roy@panix.com> - 2013-05-10 01:50 -0400
                    Re: object.enable() anti-pattern Steven D'Aprano <steve+comp.lang.python@pearwood.info> - 2013-05-10 09:47 +0000
                      Re: object.enable() anti-pattern Roy Smith <roy@panix.com> - 2013-05-10 09:22 -0400
                        Re: object.enable() anti-pattern Cameron Simpson <cs@zip.com.au> - 2013-05-11 08:25 +1000
                        Re: object.enable() anti-pattern Mark Janssen <dreamingforward@gmail.com> - 2013-05-10 20:16 -0700
                        Re: object.enable() anti-pattern Thomas Rachel <nutznetz-0c1b6768-bfa9-48d5-a470-7603bd3aa915@spamschutz.glglgl.de> - 2013-05-11 06:21 +0200
                        Re: object.enable() anti-pattern Mark Janssen <dreamingforward@gmail.com> - 2013-05-10 21:00 -0700
                  Re: object.enable() anti-pattern Dennis Lee Bieber <wlfraed@ix.netcom.com> - 2013-05-11 15:04 -0400
          Re: object.enable() anti-pattern Greg Ewing <greg.ewing@canterbury.ac.nz> - 2013-05-10 10:56 +1200
        Re: object.enable() anti-pattern Roy Smith <roy@panix.com> - 2013-05-09 09:07 -0400
          Re: object.enable() anti-pattern Oscar Benjamin <oscar.j.benjamin@gmail.com> - 2013-05-09 14:51 +0100
          Re: object.enable() anti-pattern Steven D'Aprano <steve+comp.lang.python@pearwood.info> - 2013-05-09 18:21 +0000
            Re: object.enable() anti-pattern MRAB <python@mrabarnett.plus.com> - 2013-05-09 19:34 +0100
              Re: object.enable() anti-pattern Steven D'Aprano <steve+comp.lang.python@pearwood.info> - 2013-05-10 02:30 +0000
                Re: object.enable() anti-pattern Roy Smith <roy@panix.com> - 2013-05-09 23:09 -0400
                  Re: object.enable() anti-pattern Mark Janssen <dreamingforward@gmail.com> - 2013-05-09 20:19 -0700
                  Re: object.enable() anti-pattern Chris Angelico <rosuav@gmail.com> - 2013-05-10 13:46 +1000
                  Re: object.enable() anti-pattern Steven D'Aprano <steve+comp.lang.python@pearwood.info> - 2013-05-10 05:03 +0000
                    Re: object.enable() anti-pattern Dan Sommers <dan@tombstonezero.net> - 2013-05-10 06:22 +0000
                      Re: object.enable() anti-pattern Steven D'Aprano <steve+comp.lang.python@pearwood.info> - 2013-05-10 11:00 +0000
                        Re: object.enable() anti-pattern Robert Kern <robert.kern@gmail.com> - 2013-05-10 13:19 +0100
                          Re: object.enable() anti-pattern Roy Smith <roy@panix.com> - 2013-05-10 10:01 -0400
                            Re: object.enable() anti-pattern Oscar Benjamin <oscar.j.benjamin@gmail.com> - 2013-05-10 15:29 +0100
                              Re: object.enable() anti-pattern Roy Smith <roy@panix.com> - 2013-05-10 10:37 -0400
                                Re: object.enable() anti-pattern Chris Angelico <rosuav@gmail.com> - 2013-05-11 00:46 +1000
                                  Re: object.enable() anti-pattern Roy Smith <roy@panix.com> - 2013-05-10 10:54 -0400
                                    Re: object.enable() anti-pattern Chris Angelico <rosuav@gmail.com> - 2013-05-11 01:09 +1000
                                      Re: object.enable() anti-pattern Roy Smith <roy@panix.com> - 2013-05-10 11:21 -0400
                                        Re: object.enable() anti-pattern Chris Angelico <rosuav@gmail.com> - 2013-05-11 01:44 +1000
                            Re: object.enable() anti-pattern Robert Kern <robert.kern@gmail.com> - 2013-05-10 16:33 +0100
                        Re: object.enable() anti-pattern Serhiy Storchaka <storchaka@gmail.com> - 2013-05-10 18:44 +0300
                          Re: object.enable() anti-pattern André Malo <ndparker@gmail.com> - 2013-05-11 17:33 +0200
                            Re: object.enable() anti-pattern Chris Angelico <rosuav@gmail.com> - 2013-05-12 02:04 +1000
                        Re: object.enable() anti-pattern Robert Kern <robert.kern@gmail.com> - 2013-05-10 18:20 +0100
                          Re: object.enable() anti-pattern Steven D'Aprano <steve+comp.lang.python@pearwood.info> - 2013-05-11 07:51 +0000
                            Re: object.enable() anti-pattern Roy Smith <roy@panix.com> - 2013-05-11 09:31 -0400
                            Re: object.enable() anti-pattern Robert Kern <robert.kern@gmail.com> - 2013-05-11 20:55 +0100
                            Re: object.enable() anti-pattern Chris Angelico <rosuav@gmail.com> - 2013-05-12 08:39 +1000
                        Re: object.enable() anti-pattern Chris Angelico <rosuav@gmail.com> - 2013-05-11 03:24 +1000
                        Re: object.enable() anti-pattern Mark Janssen <dreamingforward@gmail.com> - 2013-05-10 19:43 -0700
                        Re: object.enable() anti-pattern Wayne Werner <wayne@waynewerner.com> - 2013-05-12 11:48 -0500
                        Re: object.enable() anti-pattern Terry Jan Reedy <tjreedy@udel.edu> - 2013-05-12 16:23 -0400
                  Re: object.enable() anti-pattern Mark Janssen <dreamingforward@gmail.com> - 2013-05-09 20:51 -0700
                Re: object.enable() anti-pattern Chris Angelico <rosuav@gmail.com> - 2013-05-10 13:08 +1000
            Re: object.enable() anti-pattern roy@panix.com (Roy Smith) - 2013-05-09 14:59 -0400
              Re: object.enable() anti-pattern Chris Angelico <rosuav@gmail.com> - 2013-05-10 07:55 +1000
        Re: object.enable() anti-pattern Nobody <nobody@nowhere.com> - 2013-05-10 17:59 +0100
          Re: object.enable() anti-pattern Roy Smith <roy@panix.com> - 2013-05-10 13:32 -0400
            Re: object.enable() anti-pattern Dennis Lee Bieber <wlfraed@ix.netcom.com> - 2013-05-11 15:24 -0400
          Re: object.enable() anti-pattern Steven D'Aprano <steve+comp.lang.python@pearwood.info> - 2013-05-11 07:05 +0000
    Re: object.enable() anti-pattern Mark Janssen <dreamingforward@gmail.com> - 2013-05-08 19:53 -0700
    Re: object.enable() anti-pattern Mark Janssen <dreamingforward@gmail.com> - 2013-05-08 19:56 -0700
    Re: object.enable() anti-pattern Wayne Werner <wayne@waynewerner.com> - 2013-05-09 06:08 -0500
      Re: object.enable() anti-pattern Steven D'Aprano <steve+comp.lang.python@pearwood.info> - 2013-05-09 11:51 +0000
      Re: object.enable() anti-pattern Gregory Ewing <greg.ewing@canterbury.ac.nz> - 2013-05-10 11:43 +1200
        Re: object.enable() anti-pattern Michael Speer <knomenet@gmail.com> - 2013-05-09 20:18 -0400
          Re: object.enable() anti-pattern Roy Smith <roy@panix.com> - 2013-05-09 20:50 -0400
        Re: object.enable() anti-pattern Wayne Werner <wayne@waynewerner.com> - 2013-05-12 12:14 -0500
        Re: object.enable() anti-pattern Terry Jan Reedy <tjreedy@udel.edu> - 2013-05-12 16:03 -0400
        Re: object.enable() anti-pattern Greg Ewing <greg.ewing@canterbury.ac.nz> - 2013-05-13 11:18 +1200
        Re: object.enable() anti-pattern Fábio Santos <fabiosantosart@gmail.com> - 2013-05-13 07:32 +0100
        Re: object.enable() anti-pattern Chris Angelico <rosuav@gmail.com> - 2013-05-13 17:36 +1000
        Re: object.enable() anti-pattern Fábio Santos <fabiosantosart@gmail.com> - 2013-05-13 09:09 +0100
        Re: object.enable() anti-pattern Wayne Werner <wayne@waynewerner.com> - 2013-05-13 08:15 -0500

Page 3 of 5 — ← Prev page 1 2 [3] 4 5  Next page →


#45099

FromOscar Benjamin <oscar.j.benjamin@gmail.com>
Date2013-05-10 15:29 +0100
Message-ID<mailman.1530.1368196163.3114.python-list@python.org>
In reply to#45098
On 10 May 2013 15:01, Roy Smith <roy@panix.com> wrote:
> In article <mailman.1527.1368188358.3114.python-list@python.org>,
>  Robert Kern <robert.kern@gmail.com> wrote:
>
>> I'd be curious to see in-the-wild instances of the anti-pattern that
>> you are talking about, then. I think everyone agrees that entirely
>> unmotivated "enable" methods should be avoided, but I have my doubts
>> that they come up very often.
>
> As I mentioned earlier in this thread, this was a common pattern in the
> early days of C++, when exceptions were a new concept and handled poorly
> by many compilers (and, for that matter, programmers).
>
> There was a school of thought that constructors should never be able to
> fail (because the only way for a constructor to fail is to throw an
> exception).  The pattern was to always have the constructor succeed, and
> then either have a way to check to see if the newly-constructed object
> was valid, or have a separate post-construction initialization step
> which could fail.

It's not just because of exceptions. In C++ virtual method calls in a
constructor for a class A will always call the methods of class A even
if the object being constructed is actually of a subclass B because
the B part of the object isn't initialised when the A constructor is
called. There may be a better way to do this since I last used C++ but
as I remember it the two-phase pattern was a recommended way to
implement polymorphic behaviour during initialisation.


Oscar

[toc] | [prev] | [next] | [standalone]


#45100

FromRoy Smith <roy@panix.com>
Date2013-05-10 10:37 -0400
Message-ID<roy-0C2D3D.10373710052013@news.panix.com>
In reply to#45099
In article <mailman.1530.1368196163.3114.python-list@python.org>,
 Oscar Benjamin <oscar.j.benjamin@gmail.com> wrote:

> It's not just because of exceptions. In C++ virtual method calls in a
> constructor for a class A will always call the methods of class A even
> if the object being constructed is actually of a subclass B because
> the B part of the object isn't initialised when the A constructor is
> called. There may be a better way to do this since I last used C++ but
> as I remember it the two-phase pattern was a recommended way to
> implement polymorphic behaviour during initialisation.

Mind.  Blown.

One of the things I love (FSVO love) about C++ is that no matter how 
much I learn, there's always whole new areas of wonderment to explore 
behind doors I didn't even know existed.

Thank you.

I suppose, if I had a class like this, I would write a factory function 
which called the constructor and post-construction initializer.  And 
then I would make the constructor protected.

[toc] | [prev] | [next] | [standalone]


#45101

FromChris Angelico <rosuav@gmail.com>
Date2013-05-11 00:46 +1000
Message-ID<mailman.1531.1368197225.3114.python-list@python.org>
In reply to#45100
On Sat, May 11, 2013 at 12:37 AM, Roy Smith <roy@panix.com> wrote:
> I suppose, if I had a class like this, I would write a factory function
> which called the constructor and post-construction initializer.  And
> then I would make the constructor protected.

That sounds like a reasonable plan, with the possible exception of
protected. Since meeting Python, I've stopped using private and
protected anywhere.

ChrisA

[toc] | [prev] | [next] | [standalone]


#45102

FromRoy Smith <roy@panix.com>
Date2013-05-10 10:54 -0400
Message-ID<roy-E4B847.10545410052013@news.panix.com>
In reply to#45101
In article <mailman.1531.1368197225.3114.python-list@python.org>,
 Chris Angelico <rosuav@gmail.com> wrote:

> On Sat, May 11, 2013 at 12:37 AM, Roy Smith <roy@panix.com> wrote:
> > I suppose, if I had a class like this, I would write a factory function
> > which called the constructor and post-construction initializer.  And
> > then I would make the constructor protected.
> 
> That sounds like a reasonable plan, with the possible exception of
> protected. Since meeting Python, I've stopped using private and
> protected anywhere.
> 
> ChrisA

Each language has its own set of best practices.  Trying to write C++ 
code using Python patterns is as bad as trying to write Python code 
using C++ patterns.

[toc] | [prev] | [next] | [standalone]


#45104

FromChris Angelico <rosuav@gmail.com>
Date2013-05-11 01:09 +1000
Message-ID<mailman.1532.1368198547.3114.python-list@python.org>
In reply to#45102
On Sat, May 11, 2013 at 12:54 AM, Roy Smith <roy@panix.com> wrote:
> In article <mailman.1531.1368197225.3114.python-list@python.org>,
>  Chris Angelico <rosuav@gmail.com> wrote:
>
>> On Sat, May 11, 2013 at 12:37 AM, Roy Smith <roy@panix.com> wrote:
>> > I suppose, if I had a class like this, I would write a factory function
>> > which called the constructor and post-construction initializer.  And
>> > then I would make the constructor protected.
>>
>> That sounds like a reasonable plan, with the possible exception of
>> protected. Since meeting Python, I've stopped using private and
>> protected anywhere.
>>
>> ChrisA
>
> Each language has its own set of best practices.  Trying to write C++
> code using Python patterns is as bad as trying to write Python code
> using C++ patterns.

Agreed, in generality. But what is actually gained by hiding data from
yourself? Compare:

class Foo
{
    int asdf;
public:
    Foo(int _asdf):asdf(_asdf) {}
    int get_asdf() {return asdf;}
    void set_asdf(int _asdf) {asdf=_asdf;}
    void frob() {printf("Hi, I am %d\n",asdf);}
};

struct Foo
{
    int asdf;
    Foo(int _asdf):asdf(_asdf) {}
    void frob() {printf("Hi, I am %d\n",asdf);}
};

Is there anything worse about the second one? Oh, and by logical
extension, here's something that doesn't (AFAIK) work in C++, but does
in another language that's syntactically similar:

class Foo(int asdf)
{
    void frob() {write("Hi, I am %d\n",asdf);}
}

Now that's brevity. Why bother saying what's patently obvious? (Pike's
"class" keyword is like C++'s "struct", members are public by
default.)

ChrisA

[toc] | [prev] | [next] | [standalone]


#45106

FromRoy Smith <roy@panix.com>
Date2013-05-10 11:21 -0400
Message-ID<roy-3A4FCB.11213310052013@news.panix.com>
In reply to#45104
In article <mailman.1532.1368198547.3114.python-list@python.org>,
 Chris Angelico <rosuav@gmail.com> wrote:

> > Each language has its own set of best practices.  Trying to write C++
> > code using Python patterns is as bad as trying to write Python code
> > using C++ patterns.
> 
> Agreed, in generality. But what is actually gained by hiding data from
> yourself? 

You're not hiding it from yourself.  You're hiding it from the other 
people who are using your code and may not understand all the subtle 
gotchas as well as you do.

In the calling-virtual-methods-in-the-constructor case we're talking 
about here, constructing an object by calling B() without immediately 
following it up with a call to B::PostInit() is dangerous.  If you 
document that you shouldn't do that, but allow it anyway, people will do 
it.  Better to disallow it completely.  People will bitch when their 
code doesn't compile, but that's better than compiling and running and 
producing the wrong results.

You solve this problem in Python by simply having the constructor do the 
right thing.

[toc] | [prev] | [next] | [standalone]


#45111

FromChris Angelico <rosuav@gmail.com>
Date2013-05-11 01:44 +1000
Message-ID<mailman.1538.1368200674.3114.python-list@python.org>
In reply to#45106
On Sat, May 11, 2013 at 1:21 AM, Roy Smith <roy@panix.com> wrote:
> In article <mailman.1532.1368198547.3114.python-list@python.org>,
>  Chris Angelico <rosuav@gmail.com> wrote:
>>
>> Agreed, in generality. But what is actually gained by hiding data from
>> yourself?
>
> You're not hiding it from yourself.  You're hiding it from the other
> people who are using your code and may not understand all the subtle
> gotchas as well as you do.

True. And on looking over my code, I find that there are a few cases
where I've used private members: I have a buffer class that acts
pretty much like a non-refcounted string, and it declares a private
copy constructor to prevent accidental misuse. But it's the exception,
not the rule. My main point isn't about the cases where you actually
want to prevent access, but the all-too-common case where the member
itself is private and there are two public methods to get and set it.
Massive boilerplate. Completely unnecessary in 99%+ of cases.

ChrisA

[toc] | [prev] | [next] | [standalone]


#45110

FromRobert Kern <robert.kern@gmail.com>
Date2013-05-10 16:33 +0100
Message-ID<mailman.1537.1368200002.3114.python-list@python.org>
In reply to#45098
On 2013-05-10 15:01, Roy Smith wrote:
> In article <mailman.1527.1368188358.3114.python-list@python.org>,
>   Robert Kern <robert.kern@gmail.com> wrote:
>
>> I'd be curious to see in-the-wild instances of the anti-pattern that
>> you are talking about, then. I think everyone agrees that entirely
>> unmotivated "enable" methods should be avoided, but I have my doubts
>> that they come up very often.
>
> As I mentioned earlier in this thread, this was a common pattern in the
> early days of C++, when exceptions were a new concept and handled poorly
> by many compilers (and, for that matter, programmers).
>
> There was a school of thought that constructors should never be able to
> fail (because the only way for a constructor to fail is to throw an
> exception).  The pattern was to always have the constructor succeed, and
> then either have a way to check to see if the newly-constructed object
> was valid, or have a separate post-construction initialization step
> which could fail.
>
> See, for example, the isValid() and Exists() calls for RogueWave's
> RWFile class (http://tinyurl.com/c8kv26g).  And also,
> http://tinyurl.com/cgs6clx.
>
> Even today, there are C++ implementations which do not use exceptions.
> Some are for use in embedded or real-time systems where things need to
> be strictly time-bound and/or memory-bound.  Others are for historical
> reasons (http://tinyurl.com/6hn4zo).
>
> Once people were used to writing "can't fail" constructors in C++, they
> often continued using that pattern in other languages, where the
> underlying reasons no longer made sense.  Quite possibly, they never
> even knew the underlying reasons; they were taught, "Constructors must
> never fail", and assumed it was a universal rule.

Right, this is one of the "bad reasons" I talk about later in my message. The 
authors had a reason in their mind for doing it (they thought it was a universal 
rule); it was just a bad one. It's more useful to talk about why people thought 
they should follow that pattern than to just say "there is no reason to do this".

> This, BTW, is one of my biggest beefs with the classic Gang Of Four
> pattern book.  It presents a bunch of patterns as being universally
> applicable, when in reality many (if not most) of them are highly C++
> specific.
>
> BTW, whenever I read things like, "I think everyone agrees", I
> automatically assume what the writer really meant was, "I, and all the
> people who agree with me, think".

Hah! Fair enough. I actually meant it to emphasize that I thought that Steven 
was overly reducing his statements to something that was trivially true, 
sacrificing content for validity. I will suggest that your interpretation of 
that phrase is more appropriate when the speaker is proposing something of their 
own rather than (partially) conceding a point. The exaggeration is only 
self-aggrandizing in the former case.

-- 
Robert Kern

"I have come to believe that the whole world is an enigma, a harmless enigma
  that is made terrible by our own mad attempt to interpret it as though it had
  an underlying truth."
   -- Umberto Eco

[toc] | [prev] | [next] | [standalone]


#45116

FromSerhiy Storchaka <storchaka@gmail.com>
Date2013-05-10 18:44 +0300
Message-ID<mailman.1541.1368205876.3114.python-list@python.org>
In reply to#45092
10.05.13 15:19, Robert Kern написав(ла):
> I'd be curious to see in-the-wild instances of the anti-pattern that you
> are talking about, then.

Many (if not most) GUI frameworks use this pattern.

     button = Button("text")
     button.setForegroundColor(...)
     button.setBackgoundColor(...)
     button.setFont(...)
     button.setRelief(...)
     button.setBorder(...)
     button.setWidth(...)
     button.setAction(...)
     button.setMouseListener(...)
     button.place(...)

Another example is running a subprocess in Unix-like systems.

     fork()
     open/close file descriptors, set limits, etc
     exec*()

[toc] | [prev] | [next] | [standalone]


#45141

FromAndré Malo <ndparker@gmail.com>
Date2013-05-11 17:33 +0200
Message-ID<2048586.oD9bP8C7rC@news.perlig.de>
In reply to#45116
* Serhiy Storchaka wrote:

> Another example is running a subprocess in Unix-like systems.
> 
>      fork()
>      open/close file descriptors, set limits, etc
>      exec*()

For running a subprocess, only fork() is needed. For starting another
executable, only exec() is needed. For running the new executable in a
subprocess fork() and exec() are needed. I think, that's a bad example.
These APIs are actually well-designed.

nd
-- 
Gefunden auf einer "Webdesigner"-Seite:
        > Programmierung in HTML, XML, WML, CGI, FLASH <

# André Malo # http://pub.perlig.de/ #

[toc] | [prev] | [next] | [standalone]


#45144

FromChris Angelico <rosuav@gmail.com>
Date2013-05-12 02:04 +1000
Message-ID<mailman.1559.1368288293.3114.python-list@python.org>
In reply to#45141
On Sun, May 12, 2013 at 1:33 AM, André Malo <ndparker@gmail.com> wrote:
> * Serhiy Storchaka wrote:
>
>> Another example is running a subprocess in Unix-like systems.
>>
>>      fork()
>>      open/close file descriptors, set limits, etc
>>      exec*()
>
> For running a subprocess, only fork() is needed. For starting another
> executable, only exec() is needed. For running the new executable in a
> subprocess fork() and exec() are needed. I think, that's a bad example.
> These APIs are actually well-designed.

That said, though, there's certainly plenty of room for one-call APIs
like popen. For the simple case where you want to start a process with
some other executable, wait for it to finish, and then work with its
return value, it makes sense to hide the details of fork/exec/wait -
especially since that simple API can be cross-platform, where fork()
itself is quite hard to implement on Windows.

ChrisA

[toc] | [prev] | [next] | [standalone]


#45117

FromRobert Kern <robert.kern@gmail.com>
Date2013-05-10 18:20 +0100
Message-ID<mailman.1542.1368206453.3114.python-list@python.org>
In reply to#45092
On 2013-05-10 16:44, Serhiy Storchaka wrote:
> 10.05.13 15:19, Robert Kern написав(ла):
>> I'd be curious to see in-the-wild instances of the anti-pattern that you
>> are talking about, then.
>
> Many (if not most) GUI frameworks use this pattern.
>
>      button = Button("text")
>      button.setForegroundColor(...)
>      button.setBackgoundColor(...)
>      button.setFont(...)
>      button.setRelief(...)
>      button.setBorder(...)
>      button.setWidth(...)
>      button.setAction(...)
>      button.setMouseListener(...)
>      button.place(...)
>
> Another example is running a subprocess in Unix-like systems.
>
>      fork()
>      open/close file descriptors, set limits, etc
>      exec*()

According to Steven's criteria, neither of these are instances of the 
anti-pattern because there are good reasons they are this way. He is reducing 
the anti-pattern to just those cases where there is no reason for doing so. That 
is why I asked for in-the-wild instances. I should have qualified my sentence to 
include "according to your criteria" because people seem to be answering my 
query out of that context.

-- 
Robert Kern

"I have come to believe that the whole world is an enigma, a harmless enigma
  that is made terrible by our own mad attempt to interpret it as though it had
  an underlying truth."
   -- Umberto Eco

[toc] | [prev] | [next] | [standalone]


#45128

FromSteven D'Aprano <steve+comp.lang.python@pearwood.info>
Date2013-05-11 07:51 +0000
Message-ID<518df898$0$29997$c3e8da3$5496439d@news.astraweb.com>
In reply to#45117
On Fri, 10 May 2013 18:20:34 +0100, Robert Kern wrote:

> According to Steven's criteria, neither of these are instances of the
> anti-pattern because there are good reasons they are this way. He is
> reducing the anti-pattern to just those cases where there is no reason
> for doing so. 

But isn't that the case for all anti-patterns?

We agree that GOTO is an anti-pattern. That doesn't mean that there 
aren't valid reasons for using GOTO. Sometimes there are good use-cases 
for GOTO that outweigh the harm. By calling it an anti-pattern, though, 
we shift the onus onto the person wanting to use GOTO: justify why you 
need it, or use something else.

Would you object less if I called it a "code smell" than an "anti-
pattern"? If so, I accept your criticism, and call it a code smell: the 
less temporal coupling your API has, the better.


> That is why I asked for in-the-wild instances. 

How about this?

http://legacy.thecodewhisperer.com/post/366626867/improving-clarity-by-removing-temporal-coupling


Another example of temporal coupling is json_decode in PHP: you must 
follow it by a call to json_last_error, otherwise you have no way of 
telling whether the json_decode function succeeded or not.

http://php.net/manual/en/function.json-last-error.php


> I should
> have qualified my sentence to include "according to your criteria"
> because people seem to be answering my query out of that context.

Now you know how I feel :-)

I started this thread asking for help tracking down a blog post 
describing this code smell, or at least the name of such. Thanks again to 
Wayne Werner, who came through with the name of the anti-pattern, 
temporal coupling, and a blog post describing it, although not the one I 
read all those many moons ago.

I never intended to give the impression that *any* use of a separate 
"enable" method call was bad. I certainly didn't intend to be bogged 
down into a long discussion about the minutia of file descriptors in 
C, but it was educational :-)



-- 
Steven

[toc] | [prev] | [next] | [standalone]


#45129

FromRoy Smith <roy@panix.com>
Date2013-05-11 09:31 -0400
Message-ID<roy-6A558E.09311211052013@news.panix.com>
In reply to#45128
In article <518df898$0$29997$c3e8da3$5496439d@news.astraweb.com>,
 Steven D'Aprano <steve+comp.lang.python@pearwood.info> wrote:

> I never intended to give the impression that *any* use of a separate 
> "enable" method call was bad. I certainly didn't intend to be bogged 
> down into a long discussion about the minutia of file descriptors in 
> C, but it was educational :-)

Well, you did say you were here for abuse.  I think you got your money's 
worth.  Can I interest you in a course of getting hit on the head 
lessons?

And just to be clear to the studio audience and all of you who are 
watching at home...

For all the effort I put into nit-picking, I do agree with Steven's 
basic premise.  Two-phase construction is usually not the right way to 
be designing classes.  Especially in languages like Python where 
constructors raising exceptions is both inexpensive and universally 
accepted as normal behavior.

[toc] | [prev] | [next] | [standalone]


#45151

FromRobert Kern <robert.kern@gmail.com>
Date2013-05-11 20:55 +0100
Message-ID<mailman.1565.1368302161.3114.python-list@python.org>
In reply to#45128
On 2013-05-11 08:51, Steven D'Aprano wrote:
> On Fri, 10 May 2013 18:20:34 +0100, Robert Kern wrote:
>
>> According to Steven's criteria, neither of these are instances of the
>> anti-pattern because there are good reasons they are this way. He is
>> reducing the anti-pattern to just those cases where there is no reason
>> for doing so.
>
> But isn't that the case for all anti-patterns?
>
> We agree that GOTO is an anti-pattern. That doesn't mean that there
> aren't valid reasons for using GOTO. Sometimes there are good use-cases
> for GOTO that outweigh the harm. By calling it an anti-pattern, though,
> we shift the onus onto the person wanting to use GOTO: justify why you
> need it, or use something else.

Yes, that was the point I was making. You seemed to be defining away the 
legitimate instances as not instances of the pattern at all because they were 
legitimate, and that appeared to me to be defeating the purpose of having the 
discussion.

On a related note, I *don't* think it's a good idea to phrase it as "justify why 
you need it". I don't think that gives very good guidance to a novice when they 
are given the task of designing something. People can come up with a 
justification for just about anything, especially when they are only justifying 
things to themselves. I think it's more important to just talk about the 
situations where a pattern is useful, and the common situations where people, 
for whatever reason, *think* that a pattern is useful, but isn't. Naming it a 
Pattern or Anti-pattern is really just a measure of how bad the latter half of 
that is compared to the first half, and is less interesting than the discussion 
itself. That's why I had a bug up my ass about what looked like the exclusion of 
the "good" uses. It's the good examples that give novices an idea of what a good 
justification looks like, so they can tell if the justification they are giving 
themselves measures up.

> Would you object less if I called it a "code smell" than an "anti-
> pattern"? If so, I accept your criticism, and call it a code smell: the
> less temporal coupling your API has, the better.

That was not really my objection. I was objecting to the way you appeared to be 
defining the particular pattern in question. But as we appear to agree on the 
important matters, I won't press it further.

>> That is why I asked for in-the-wild instances.
>
> How about this?
>
> http://legacy.thecodewhisperer.com/post/366626867/improving-clarity-by-removing-temporal-coupling

There's something about Java mixedCase that makes my eyes glaze, so I'll take 
your word for it. :-)

> Another example of temporal coupling is json_decode in PHP: you must
> follow it by a call to json_last_error, otherwise you have no way of
> telling whether the json_decode function succeeded or not.
>
> http://php.net/manual/en/function.json-last-error.php

I suspect that the author might say something about error checking being 
optional. But yeah, terrible API. :-)

-- 
Robert Kern

"I have come to believe that the whole world is an enigma, a harmless enigma
  that is made terrible by our own mad attempt to interpret it as though it had
  an underlying truth."
   -- Umberto Eco

[toc] | [prev] | [next] | [standalone]


#45159

FromChris Angelico <rosuav@gmail.com>
Date2013-05-12 08:39 +1000
Message-ID<mailman.1569.1368311970.3114.python-list@python.org>
In reply to#45128
On Sun, May 12, 2013 at 5:55 AM, Robert Kern <robert.kern@gmail.com> wrote:
>> Another example of temporal coupling is json_decode in PHP: you must
>> follow it by a call to json_last_error, otherwise you have no way of
>> telling whether the json_decode function succeeded or not.
>>
>> http://php.net/manual/en/function.json-last-error.php
>
>
> I suspect that the author might say something about error checking being
> optional. But yeah, terrible API. :-)

The problem with that one isn't that error checking is optional, but
that errors are signalled with a perfectly legal return value (FALSE,
if I recall correctly - which is also returned if you json_decode a
boolean false). Better design would either throw an exception on
error, or have a unique sentinel representing the errorness of the
return value.

ChrisA

[toc] | [prev] | [next] | [standalone]


#45118

FromChris Angelico <rosuav@gmail.com>
Date2013-05-11 03:24 +1000
Message-ID<mailman.1543.1368206709.3114.python-list@python.org>
In reply to#45092
On Sat, May 11, 2013 at 1:44 AM, Serhiy Storchaka <storchaka@gmail.com> wrote:
> 10.05.13 15:19, Robert Kern написав(ла):
>
>> I'd be curious to see in-the-wild instances of the anti-pattern that you
>> are talking about, then.
>
>
> Many (if not most) GUI frameworks use this pattern.
>
>     button = Button("text")
>     button.setForegroundColor(...)
>     button.setBackgoundColor(...)
>     button.setFont(...)
>     button.setRelief(...)
>     button.setBorder(...)
>     button.setWidth(...)
>     button.setAction(...)
>     button.setMouseListener(...)
>     button.place(...)

The button really exists, though. You could merge the creation and
placement (or in the case of a window/dialog, the creation and
showing), but it's often useful to not. However, in the specific case
you have there, there's an alternative: a mapping of attributes and
values passed to the constructor, and then you could pass the
constructed object directly to a place(). That would let you, if you
wished, effectively construct a Button with a parent right there,
which makes reasonable sense.

> Another example is running a subprocess in Unix-like systems.
>
>     fork()
>     open/close file descriptors, set limits, etc
>     exec*()

Hrm. Not really a corresponding example. After you fork, you have two
actual processes. Following up with an exec is only one of the
possible options; I've done code that forks and execs, and code that
forks and keeps running, and neither of them feels "wrong" in any way.
There IS a function that's similar to what you're saying, and that's
vfork:

"""
(From POSIX.1) The vfork() function has the same effect as fork(2),
except that the behavior is undefined if the process created by
vfork() either modifies any data other than a variable of type pid_t
used to store the return value from vfork(), or returns from the
function in which vfork() was called, or calls any other function
before successfully calling _exit(2) or one of the exec(3) family of
functions.
"""

It's deprecated because it's so fragile (and because regular fork()
isn't that much less efficient now; AIUI, vfork was meant to be a
lightweight fork). I would say that the deprecation of vfork in favour
of fork is a strong indication that the object.enable() anti-pattern
can come up in kernel APIs too, and isn't liked there either.

ChrisA

[toc] | [prev] | [next] | [standalone]


#45123

FromMark Janssen <dreamingforward@gmail.com>
Date2013-05-10 19:43 -0700
Message-ID<mailman.1545.1368240240.3114.python-list@python.org>
In reply to#45092
>>>>>> There is no sensible use-case for creating a file OBJECT unless it
>>>>>> initially wraps an open file pointer.
>>
>>> So far the only counter-examples given aren't counter-examples ...
>>
>> Well, sure, if you discount operations like "create this file" and
>> queries like "could I delete this file if I wanted to?" [0] as methods
>> of the file system rather than of a hypothetical file object.
>>
>> What about a distributed system?  Suppose I want to create a file object
>> in one place, and send that object to the another place for the file to
>> be read from or written to [1]?  Suppose that for security reasons, I
>> have to do it that way, because the first place can only create the
>> objects, and the second place can only access the underly file contents
>> through an existing object?
>
> Unless you have re-implemented the file I/O system, it doesn't matter. If
> your file objects are based on C I/O, then even if the first server
> cannot read or write to the files it still creates file objects in an
> open state, because that is how C works.
>
> Or maybe the first server only creates some sort of proxy to the real
> underlying file object. Or maybe you're re-implemented the I/O system,
> and aren't following C's design. Since you're just making this up as a
> thought experiment, anything is possible.
>
> But either way, that's fine. You've found an object where it does make
> sense to have an explicit "make it go" method: first one entity has
> permission to construct the object, but not to open the underlying file.
> Another entity has permission to open the underlying file, but not to
> create the object. I have no idea whether this is a reasonable security
> design or not, it actually sounds a bit rubbish to me but what do I know?
> So let's treat it as a reasonable design.
>
> As I've said, repeatedly, that's not what I'm talking about.
>
> When you DON'T have useful things that can be done with the object before
> calling "enable", then it is an anti-pattern to require a separate call
> to "enable" method, and the enable functionality should be moved into the
> object constructor. If you DO have useful things that can be done, like
> pass the object to another entity, for security, then that's a whole
> 'nuther story.

You're missing one other case:  if there's useful things that can be
checked before calling enable().  Remember, on multi-user and/or
multi-processing systems, there could be contention for a slow
resource.  If you automatically open a file for write, you're
preventing everyone else from writing and potentially reading it.  So
there is something useful: did that file exist?  Is that resource
available for writing?

Prior to such hi-level languages like Python and reliable hardware,
such fine-grained control was important and vital.   Now it can
probably be relegated to special OS libraries.

Mark

> Really, what I'm describing is *normal* behaviour for most objects. We
> don't usually design APIs like this:
>
> n = int("42")
> n.enable()
> m = n + 1
> m.enable()
> x = m*2 + n*3
> print x - 1  # oops, raises because I forgot to call x.enable()

Again, you only do that for shared resources.  In this case, memory
would have to be a (protected) shared resources, but the OS manages
memory allocation, so it's not an issue.

Mark

> That's a rubbish API, and for simple data-like objects, we all agree it
> is a rubbish API. So why accept the same rubbish API just because the
> object is more complicated?

I think I just told you, but let me know ..... :)

> For my next controversial opinion, I'm going to argue that we should do
> arithmetic using numbers rather than by inserting lists inside other
> lists:

Try arguing that we should have a common message-passing syntax.

> # Do this:
>
> count = 0
> count += 1
>
> # Not this:
>
> count = []
> count.insert(0, [])

That's actually what they do in "set theory", believe it or not.

MarkJ
Tacoma, Washington

[toc] | [prev] | [next] | [standalone]


#45188

FromWayne Werner <wayne@waynewerner.com>
Date2013-05-12 11:48 -0500
Message-ID<mailman.1585.1368377382.3114.python-list@python.org>
In reply to#45092
On Fri, 10 May 2013, Robert Kern wrote:

> On 2013-05-10 12:00, Steven D'Aprano wrote:
>
>> But either way, that's fine. You've found an object where it does make
>> sense to have an explicit "make it go" method: first one entity has
>> permission to construct the object, but not to open the underlying file.
>> Another entity has permission to open the underlying file, but not to
>> create the object. I have no idea whether this is a reasonable security
>> design or not, it actually sounds a bit rubbish to me but what do I know?
>> So let's treat it as a reasonable design.
>> 
>> As I've said, repeatedly, that's not what I'm talking about.
>> 
>> When you DON'T have useful things that can be done with the object before
>> calling "enable", then it is an anti-pattern to require a separate call
>> to "enable" method, and the enable functionality should be moved into the
>> object constructor. If you DO have useful things that can be done, like
>> pass the object to another entity, for security, then that's a whole
>> 'nuther story.
>
> I'd be curious to see in-the-wild instances of the anti-pattern that you are 
> talking about, then. I think everyone agrees that entirely unmotivated 
> "enable" methods should be avoided, but I have my doubts that they come up 
> very often. Do programmers have a natural tendency to make an extra, 
> completely unnecessary method? I would think that they have a natural 
> tendency to the opposite.
>
> In my experience, everyone has a reason in mind when they follow a 
> pattern/anti-pattern. It is pretty rare that someone just does some specific, 
> nameable thing for no reason at all. There is no need to call out an 
> anti-pattern for which no one has a reason to do it. But there is a continuum 
> of reasons. Some reasons are better than others. Some reasons only apply in a 
> small set of circumstances but seem like they would apply more generally, at 
> least to novice programmers. Programmers can be wrong about what they think 
> the (anti-)pattern actually achieves. The whole point of naming an 
> anti-pattern is to discuss those reasons, show where they are misapplied, 
> where YAGNI, why novices overuse it, other patterns that should be used 
> instead, and also the circumstances where it is actually a good pattern 
> instead.

I'll share the anti-pattern that I've seen many times (not actually in 
Python)

     class CoolPresenter:
         def __init__(self):
             self.view = None
             self.some_property = None
             self.other_property = None

         def initialize(self):
             self.view.disable()
             data = self.load_data()
             self.view.data = data
             self.view.enable()


         def reload(self):
             if self.view is None:
                 raise NotInitializedError("Error: Please setup class")
             self.view.disable()
             data = self.load_data()
             self.view.data = data
             self.view.enable()



Then you would see code like this:

     presenter = CoolPresenter()
     presenter.view = CoolView()

This is just plain silly for a few reasons:

     - It's ambiguous. I don't know what's required for the CoolPresenter
       to function properly.

     - The temporal coupling mentioned earlier. I can create an instance of
       a class and then call a function (say `reload`) and then boom! My
       program crashes. There is *no possible* use case of this class where
       you can use it without a view.


The motivation behind this anti-pattern that I've seen is the desire to 
not have a constructor that "does too much". So you end out with an empty 
constructor and temporal coupling, and a terrible API that doesn't clearly 
explain the requirements of the class. Your class constructor should 
*require* everything that is necessary to have a stable state when the 
class is created (i.e. you should be able to properly call any function, 
set any property without an exception happening)

Why? Less bugs, easier to comprehend, change/update your code. Easier to 
use the class.

-W

[toc] | [prev] | [next] | [standalone]


#45200

FromTerry Jan Reedy <tjreedy@udel.edu>
Date2013-05-12 16:23 -0400
Message-ID<mailman.1595.1368390250.3114.python-list@python.org>
In reply to#45092
On 5/12/2013 12:48 PM, Wayne Werner wrote:

> I'll share the anti-pattern that I've seen many times (not actually in
> Python)
>
>      class CoolPresenter:
>          def __init__(self):
>              self.view = None
>              self.some_property = None
>              self.other_property = None
>
>          def initialize(self):
>              self.view.disable()
>              data = self.load_data()
>              self.view.data = data
>              self.view.enable()
>
>
>          def reload(self):
>              if self.view is None:
>                  raise NotInitializedError("Error: Please setup class")
>              self.view.disable()
>              data = self.load_data()
>              self.view.data = data
>              self.view.enable()
>
>
>
> Then you would see code like this:
>
>      presenter = CoolPresenter()
>      presenter.view = CoolView()
>
> This is just plain silly for a few reasons:
>
>      - It's ambiguous. I don't know what's required for the CoolPresenter
>        to function properly.
>
>      - The temporal coupling mentioned earlier. I can create an instance of
>        a class and then call a function (say `reload`) and then boom! My
>        program crashes. There is *no possible* use case of this class where
>        you can use it without a view.

Thank you for this examples. It makes Steven's point clearer than the 
'file object' example. The problem with the latter is that objectors 
could could point to file path objects, which are used to do some 
manipulations of files and directory entries on the storage device 
*without looking at the specific file data*. Such file data, as opposed 
to directory data, never enters the program data space and with smart 
enough devices, need not enter the CPU and its RAM. They could then 
confuse them with file access objects which are used to transfer 
specific data *between* files and program data space. The confusion is 
adied by the fact that file path objects are used to create file access 
objects. They do separate jobs and it seems that they are well kept 
separate, even if there are languages where file path objects can have 
file access functions turned on and off.

> The motivation behind this anti-pattern that I've seen is the desire to
> not have a constructor that "does too much". So you end out with an
> empty constructor and temporal coupling, and a terrible API that doesn't
> clearly explain the requirements of the class. Your class constructor
> should *require* everything that is necessary to have a stable state
> when the class is created (i.e. you should be able to properly call any
> function, set any property without an exception happening)
>
> Why? Less bugs, easier to comprehend, change/update your code. Easier to
> use the class.


--
Terry Jan Reedy


[toc] | [prev] | [next] | [standalone]


Page 3 of 5 — ← Prev page 1 2 [3] 4 5  Next page →

Back to top | Article view | comp.lang.python


csiph-web