On 2014-01-16 16:18, Roy Smith wrote:
On Thursday, January 16, 2014 10:46:10 AM UTC-5, Robert Kern wrote:

I prefer to keep my __init__() methods as dumb as possible to retain the
flexibility to construct my objects in different ways. Sure, it's convenient to,
say, pass a filename and have the __init__() open() it for me. But then I'm
stuck with only being able to create this object with a true, named file on
disk. I can't create it with a StringIO for testing, or by opening a file and
seeking to a specific spot where the relevant data starts, etc. I can keep the
flexibility and convenience by keeping __init__() dumb and relegating various
smarter and more convenient ways to instantiate the object to classmethods.

There's two distinct things being discussed here.

The idea of passing a file-like object vs. a filename gives you flexibility, 
that's for sure.  But, that's orthogonal to how much work should be done in the 
constructor.  Consider this class:

Where the two get conflated is that both lead to advice that looks the same (or at least can get interpreted the same by newbies who are trying to learn and don't have the experience to pick out the subtleties): "do nothing in __init__". That's why I am trying to clarify where this advice might be coming from and why at least one version of it may be valid.

class DataSlurper:
     def __init__(self):
         self.slurpee = None

     def attach_slurpee(self, slurpee):
         self.slurpee = slurpee

     def slurp(self):
         for line in self.slurpee:
             # whatever

This exhibits the nice behavior you describe; you can pass it any iterable, not just a 
file, so you have a lot more flexibility.  But, it's also exhibiting what many people 
call the "two-phase constructor" anti-pattern.  When you construct an instance 
of this class, it's not usable until you call attach_slurpee(), so why not just do that 
in the constructor?

That's where my recommendation of classmethods come in. The result of __init__() should always be usable. It's just that its arguments may not be as convenient as you like because you pass in objects that are closer to the internal representation than you normally want to deal with (e.g. file objects instead of filenames). You make additional constructors (initializers, whatever) as classmethods to restore convenience.


class DataSlurper:
  def __init__(self, slurpee):
    self.slurpee = slurpee

  @classmethod
  def fromfile(cls, filename):
    slurpee = open(filename)
    return cls(slurpee)

  @classmethod
  def fromurl(cls, url):
    slurpee = urllib.urlopen(url)
    return cls(slurpee)

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

--
https://mail.python.org/mailman/listinfo/python-list

Reply via email to