object.enable() anti-pattern

  • Thread starter Steven D'Aprano
  • Start date
S

Steven D'Aprano

I'm looking for some help in finding a term, it's not Python-specific but
does apply to some Python code.

This is an anti-pattern to avoid. The idea is that creating a resource
ought to be the same as "turning it on", or enabling it, or similar. For
example, we don't do this in Python:


f = file("some_file.txt")
f.open()
data = f.read()


because reading the file can fail if you forget to call open first.
Instead, Python uses a factory function that creates the file object and
opens it:

f = open("some_file.txt") # if this succeeds, f is ready to use
data = f.read()


Basically, any time you have two steps required for using an object, e.g.
like this:

obj = someobject(arg1, arg2)
obj.enable()

you should move the make-it-work functionality out of the enable method
and into __init__ so that creating the object creates it in a state ready
to work.

I read a blog some time ago (a year ago?) that discusses this anti-
pattern, and I'm pretty sure gave it a specific name, but I cannot find
it again. Can anyone find any references to this anti-pattern? My google-
fu is letting me down.



(For what it's worth, I'm refactoring some of my own code that falls into
this anti-pattern. Die, enable method, die die die!)
 
R

Robert Kern

I'm looking for some help in finding a term, it's not Python-specific but
does apply to some Python code.

This is an anti-pattern to avoid. The idea is that creating a resource
ought to be the same as "turning it on", or enabling it, or similar.

I don't think the anti-pattern has a name, but it's opposite pattern is named:

http://en.wikipedia.org/wiki/Resource_Acquisition_Is_Initialization

--
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
 
S

Steven D'Aprano

I don't think the anti-pattern has a name, but it's opposite pattern is
named:

http://en.wikipedia.org/wiki/Resource_Acquisition_Is_Initialization

That sounds like it should be related, but it actually isn't since RAII
actually has little to do with *acquiring* the resource and everything to
do with *releasing* it. See, for example:

http://stackoverflow.com/questions/2321511/what-is-meant-by-resource-
acquisition-is-initialization-raii

where it is pointed out that the resource may be acquired outside of the
object constructor and passed in as an argument. RAII is actually about
deterministic destruction of objects and the release of their resources.

But thanks for the attempt :)
 
R

Roy Smith

Steven D'Aprano said:
I'm looking for some help in finding a term, it's not Python-specific but
does apply to some Python code.

This is an anti-pattern to avoid. The idea is that creating a resource
ought to be the same as "turning it on", or enabling it, or similar. For
example, we don't do this in Python:


f = file("some_file.txt")
f.open()
data = f.read()

I've worked with C++ code that did this. At one point in the evolution
of OOP group consciousness, there was a feeling that constructors must
never fail. I don't remember if it was a general language-agnostic
pattern, or a specific C++ reaction to poor exception handling code in
early compilers. What came out of that was the pattern you describe.
All the code that could fail was factored out of the constructor into an
"enable" method.

That being said, sometimes there are good reasons for doing this. One
example might be something like:

frobnicator = Frobnicator()
for file in my_file_list:
frobnicator.munch(file)
for line in frobnicator:
process(line)

If creating a Frobnicator instance is very expensive, it might pay to
create an instance once and keep reusing it on multiple files. Here,
munch() is your enable() method. But, that's not quite what you were
talking about.
 
M

Mark Janssen

I'm looking for some help in finding a term, it's not Python-specific but
does apply to some Python code.

This is an anti-pattern to avoid. The idea is that creating a resource
ought to be the same as "turning it on", or enabling it, or similar. For
example, we don't do this in Python:

I would call it "user-mediated resource allocation" as distinct from
"object-mediated" resource allocation.
 
M

Mark Janssen

This is an anti-pattern to avoid. The idea is that creating a resource
I would call it "user-mediated resource allocation" as distinct from
"object-mediated" resource allocation.

I should point out, though, it's not really an anti-pattern (coming
form the C community).
 
S

Steven D'Aprano

I've come across this under the name 'two-phase construction', but as a
desirable(!?) pattern rathern than as an anti-pattern.

In particular Symbian used it throughout as originally their C++
implementation didn't support exceptions. Instead they had a separate
cleanup stack and objects that require cleanup were pushed onto that
stack after being fully constructed but before calling the
initialisation that required cleanup. See
http://www.developer.nokia.com/Community/Wiki/Two-phase_construction



Thanks for the link. It's obviously not the blog post I was looking for,
but it is interesting.
 
S

Steven D'Aprano

So why don't we do this?

data = read("some_file.txt")

Because there is a lot more that you might want to do to a file than just
read from it.

py> f = open('/tmp/x')
py> dir(f)
['_CHUNK_SIZE', '__class__', '__delattr__', '__dict__', '__dir__',
'__doc__', '__enter__', '__eq__', '__exit__', '__format__', '__ge__',
'__getattribute__', '__getstate__', '__gt__', '__hash__', '__init__',
'__iter__', '__le__', '__lt__', '__ne__', '__new__', '__next__',
'__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__',
'__str__', '__subclasshook__', '_checkClosed', '_checkReadable',
'_checkSeekable', '_checkWritable', 'buffer', 'close', 'closed',
'detach', 'encoding', 'errors', 'fileno', 'flush', 'isatty',
'line_buffering', 'mode', 'name', 'newlines', 'read', 'readable',
'readline', 'readlines', 'seek', 'seekable', 'tell', 'truncate',
'writable', 'write', 'writelines']


That's 24 public methods and attributes, excluding private and dunder
attributes.

There is no sensible use-case for creating a file without opening it.
What would be the point? Any subsequent calls to just about any method
will fail. Since you have to open the file after creating the file object
anyway, why make them two different calls?

Besides, this is not to denigrate the idea of a read() function that
takes a filename and returns its contents. But that is not an object
constructor. It may construct a file object internally, but it doesn't
return the file object, so it is completely unrelated to the scenario I
described.


That's just the "enable" paradigm in a socially acceptable and
traditional wrapper.

Well duh. That's like saying that for loops, while loops and co-routines
are just GOTO in a socially acceptable and traditional wrapper. Of course
they are. That's the whole point.
 
S

Steven D'Aprano

Think of spinning off a thread: initialize it synchronously, and then
let it execute asynchronously. We tend towards that pattern even when
we know that execution will be synchronous, because we also that it
could be asynchronous one day.

Whether it is synchronous or asynchronous is irrelevant.

I can see use-cases for separating "make it go" from initialisation. It
all depends on what you might want to do to the object before making it
go. If the answer is "Nothing", then there is no reason not to have the
constructor make it go. If the answer is, "Well, sometimes we need to do
things to the object before making it go", then it makes sense to
separate the two:

blob = MyBlob(arg1, arg2, agr3)
blob.poke("with a stick")
blob.append(data)
blob.foo = "spam"
blob.make_it_go()


I'm not talking about this case. I'm talking about the case where there's
nothing you can do with the blob before making it go.

Yes, we could wrap that up in a neat
bundle, but explicit is better than implicit.

"And that is why we always write code like this:

n = int(int(len(something)) + int(1))

just to be sure that the result is explicitly an int and not just
implicitly an int. Suppose some Javascript programmer was reading the
code, and they thought that 1 was a floating point value. That would be
bad!"
 
C

Chris Angelico

I can see use-cases for separating "make it go" from initialisation. It
all depends on what you might want to do to the object before making it
go. If the answer is "Nothing", then there is no reason not to have the
constructor make it go. If the answer is, "Well, sometimes we need to do
things to the object before making it go", then it makes sense to
separate the two:

blob = MyBlob(arg1, arg2, agr3)
blob.poke("with a stick")
blob.append(data)
blob.foo = "spam"
blob.make_it_go()

Example use-case: Most GUI frameworks. You create a window, then
populate it, then show it. When you create the window object in
Python, you expect an actual window to exist, it should have its
handle etc. So it's still the same thing; the object is fully created
in its constructor.

ChrisA
 
T

Terry Jan Reedy

Besides, this is not to denigrate the idea of a read() function that
takes a filename and returns its contents. But that is not an object
constructor. It may construct a file object internally, but it doesn't
return the file object, so it is completely unrelated to the scenario I
described.

At least a few stdlib modules that define classes *also* have such
functions. They create an instance of the class, call one or more
methods, and return the result of the method, discarding the instance.
For instance, see the subprocess module, its POpen class, and module
functions; or the timeit module, its Timer class, and functions.
 
G

Gregory Ewing

Steven said:
There is no sensible use-case for creating a file without opening it.
What would be the point?

Early unix systems often used this as a form of locking.
 
C

Cameron Simpson

| Steven D'Aprano wrote:
| >There is no sensible use-case for creating a file without opening
| >it. What would be the point?
|
| Early unix systems often used this as a form of locking.

Not just early systems: it's a nice lightweight method of making a
lockfile even today if you expect to work over NFS, where not that
many things are synchronous. You open a file with "0" modes, so
that it is _immediately_ not writable. Other attempts to make the
lock file thus fail because of the lack of write, even over NFS.

Cheers,
 
W

Wayne Werner

I'm looking for some help in finding a term, it's not Python-specific but
does apply to some Python code.

This is an anti-pattern to avoid. The idea is that creating a resource
ought to be the same as "turning it on", or enabling it, or similar. For
example, we don't do this in Python:

I'm not entirely sure what the name of it is, but the basic concept is
that you should never partially create, or create a class that can be in
an unstable state. Which isn't to say you should prevent invalid input,
only that with every valid input or single operation (including
construction) your class should be valid.


Ah, that's it - the problem is that it introduces /Temporal Coupling/ to
one's code: http://blog.ploeh.dk/2011/05/24/DesignSmellTemporalCoupling/

You don't ever want a class that has functions that need to be called in a
certain order to *not* crash. That's fine if you have to call them in a
certain sequence in order to get the correct data - that's what
programming is all about, after all. But if you provide me a class with a
constructor you better make sure that when I do this:

thing = YourSuperAwesomeClass()
thing.do_stuff()

that I don't get some horrid stack trace ending with

InvalidStateError: initialize() needs to be called before do_stuff()

Or something worse.


HTH,
Wayne

p.s. I'm interested in reading whatever is evenually written on the topic
 
S

Steven D'Aprano

| Steven D'Aprano wrote:
| > There is no sensible use-case for creating a file WITHOUT OPENING
| > it. What would be the point?
|
| Early unix systems often used this as a form of locking.

Not just early systems: it's a nice lightweight method of making a
lockfile even today if you expect to work over NFS, where not that many
things are synchronous. You OPEN A FILE with "0" modes

[emphasis added]

This is all very well and good, but for the life of me, I cannot see how
opening a file is a good example of not opening a file. Perhaps it is a
Zen thing, like the sound no spoon makes when you don't tap it against a
glass that isn't there.
 
S

Steven D'Aprano

Ah, that's it - the problem is that it introduces /Temporal Coupling/ to
one's code: http://blog.ploeh.dk/2011/05/24/DesignSmellTemporalCoupling/

Good catch!

That's not the blog post I read, but that's the same concept. "Temporal
Coupling" -- yes, that is an excellent description of the problem.

You don't ever want a class that has functions that need to be called in
a certain order to *not* crash. That's fine if you have to call them in
a certain sequence in order to get the correct data - that's what
programming is all about, after all. But if you provide me a class with
a constructor you better make sure that when I do this:

thing = YourSuperAwesomeClass()
thing.do_stuff()

that I don't get some horrid stack trace ending with

InvalidStateError: initialize() needs to be called before
do_stuff()

Or something worse.

Exactly.


Thank you Wayne, that's a great help.
 
R

Roy Smith

Steven D'Aprano said:
There is no sensible use-case for creating a file without opening it.

Sure there is. Sometimes just creating the name in the file system is
all you want to do. That's why, for example, the unix "touch" command
exists.
 
O

Oscar Benjamin

Sure there is. Sometimes just creating the name in the file system is
all you want to do. That's why, for example, the unix "touch" command
exists.

Wouldn't the code that implements the touch command just look
something like this:

f = open(filename)
f.close()

Or is there some other way of creating the file that doesn't open it
(I mean in general not just in Python)?


Oscar
 
S

Steven D'Aprano

Sure there is. Sometimes just creating the name in the file system is
all you want to do. That's why, for example, the unix "touch" command
exists.

Since I neglected to make it clear above that I was still talking about
file objects, rather than files on disk, I take responsibility for this
misunderstanding. I thought that since I kept talking about file
*objects* and *constructors*, people would understand that I was talking
about in-memory objects rather than on-disk files. Mea culpa.

So, let me rephrase that sentence, and hopefully clear up any further
misunderstandings.

There is no sensible use-case for creating a file OBJECT unless it
initially wraps an open file pointer.

This principle doesn't just apply to OOP languages. The standard C I/O
library doesn't support creating a file descriptor unless it is a file
descriptor to an open file. open() has the semantics:

"It shall create an open file description that refers to a file and a
file descriptor that refers to that open file description."

http://pubs.opengroup.org/onlinepubs/9699919799/functions/open.html

and there is no corresponding function to create a *closed* file
description. (Because such a thing would be pointless.)

Of course language designers are free to design their language to work
under whatever anti-patterns they desire. I quote from the Pascal
Language Reference:

"open associates the permanent file file [sic] with a file variable for
reading or writing. open does not actually open the file; you must call
reset or rewrite before reading or writing to that file."

http://www.amath.unc.edu/sysadmin/DOC4.0/pascal/lang_ref/
ref_builtin.doc.html


but since that's not a part of standard Pascal, other Pascals may behave
differently.
 
M

MRAB

Since I neglected to make it clear above that I was still talking about
file objects, rather than files on disk, I take responsibility for this
misunderstanding. I thought that since I kept talking about file
*objects* and *constructors*, people would understand that I was talking
about in-memory objects rather than on-disk files. Mea culpa.

So, let me rephrase that sentence, and hopefully clear up any further
misunderstandings.

There is no sensible use-case for creating a file OBJECT unless it
initially wraps an open file pointer.
You might want to do this:

f = File(path)
if f.exists():
...

This would be an alternative to:

if os.path.exists(path):
...
This principle doesn't just apply to OOP languages. The standard C I/O
library doesn't support creating a file descriptor unless it is a file
descriptor to an open file. open() has the semantics:

"It shall create an open file description that refers to a file and a
file descriptor that refers to that open file description."

http://pubs.opengroup.org/onlinepubs/9699919799/functions/open.html

and there is no corresponding function to create a *closed* file
description. (Because such a thing would be pointless.)
[snip]
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,768
Messages
2,569,574
Members
45,051
Latest member
CarleyMcCr

Latest Threads

Top